I also had this requirement and I managed to figure out how to do it. One way would be to parse the generated C# text code and do a simple string replacement of "public class Template" with "public class MyOwnNameForTemplate". However, I think is better and safer to do it on the SyntaxTree object which has already parsed the C# code right before the compilation with Roslyn compiler. Find bellow full code:
namespace MyOwnNameSpace
{
//you can specify @inherit in your cshtml view to inherit your custom base class. If you do that, make sure that class has some dummy methods which are used by Visual Studio when it tries to compile your .cshtml
/*
public virtual void Write(object obj)
{
}
public async virtual Task ExecuteAsync()//this is the root method which should be called for your compiled view
{
}
public virtual void WriteLiteral(string literal = null)//the compiled view will emit calls to this method to write the literal HTML. Is pity that Razor does not emit async call to this
{
}
//bellow methods are needed by Visual Studio
protected T CreateTagHelper<T>()
{
throw new Exception("CreateTagHelper");
}
protected void StartTagHelperWritingScope(System.Text.Encodings.Web.HtmlEncoder htmlEncoder)
{
throw new Exception("StartTagHelperWritingScope");
}
protected Func<TagHelperContent> EndTagHelperWritingScope => throw new Exception("EndTagHelperWritingScope");
*/
public class TestTemplateCompilation
{
private List<MetadataReference> GetAssemblyReferences()
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
bool isFullFramework = RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase);
HashSet<Assembly> referencedAssemblies = null;
if (isWindows && isFullFramework)
{
referencedAssemblies = new HashSet<Assembly>()
{
typeof(object).Assembly,
Assembly.Load(new AssemblyName("Microsoft.CSharp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")),
this.GetType().Assembly,//if you have specified a base class in your template, a class which is located in this assembly
typeof(System.Runtime.GCSettings).Assembly,
typeof(System.Linq.Enumerable).Assembly,
typeof(System.Linq.Expressions.Expression).Assembly,
Assembly.Load(new AssemblyName("netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"))
};
}
if (isWindows && !isFullFramework) // i.e. NETCore
{
referencedAssemblies = new HashSet<Assembly>()
{
typeof(object).Assembly,
Assembly.Load(new AssemblyName("Microsoft.CSharp")),
this.GetType().Assembly,//if you have specified a base class in your template, a class which is located in this assembly
Assembly.Load(new AssemblyName("System.Runtime")),
Assembly.Load(new AssemblyName("System.Linq")),
Assembly.Load(new AssemblyName("System.Linq.Expressions")),
Assembly.Load(new AssemblyName("netstandard"))
};
}
if (!isWindows)
{
referencedAssemblies = new HashSet<Assembly>()
{
typeof(object).Assembly,
Assembly.Load(new AssemblyName("Microsoft.CSharp")),
this.GetType().Assembly,//if you have specified a base class in your template, a class which is located in this assembly
Assembly.Load(new AssemblyName("System.Runtime")),
Assembly.Load(new AssemblyName("System.Linq")),
Assembly.Load(new AssemblyName("System.Linq.Expressions")),
Assembly.Load(new AssemblyName("netstandard"))
};
}
return referencedAssemblies.Select(ass => (MetadataReference)MetadataReference.CreateFromFile(ass.Location)).ToList();
}
public MemoryStream CompileTemplate(String razorCSHTMLFilePath)
{
var templateContentLines = File.ReadAllLines(razorCSHTMLFilePath);
var templateContent = String.Join("\r\n", templateContentLines.Where(l => !l.Contains("@model", StringComparison.OrdinalIgnoreCase)));//remove @model declaration since it can give compilation error
RazorProjectEngine engine = RazorProjectEngine.Create(
RazorConfiguration.Default,
RazorProjectFileSystem.Create(@"."),
(builder) =>
{
builder.SetNamespace("MyMainNameSpace");
});
string fileName = Path.GetRandomFileName();
RazorSourceDocument document = RazorSourceDocument.Create(templateContent, fileName);
RazorCodeDocument codeDocument = engine.Process(
document,
null,
new List<RazorSourceDocument>(),
new List<TagHelperDescriptor>());
RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument();
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(razorCSharpDocument.GeneratedCode);
//here starts the "magic" of replacing the class name which you need
var rootNode = syntaxTree.GetRoot();
ClassDeclarationSyntax classDeclarationNode = rootNode.DescendantNodes((node) => true).OfType<ClassDeclarationSyntax>().Single();//make sure you don't have declared class inside your .cshtml view
var classNameToken = classDeclarationNode.DescendantTokens().Single(t => t.Kind() == SyntaxKind.IdentifierToken);//get the original class declaration token
var newClassNameToken = SyntaxFactory.Identifier("MyCustomClassName");//specify you class name
var newClassDeclarationNode = classDeclarationNode.ReplaceToken(classNameToken, newClassNameToken);
var newRootNode = rootNode.ReplaceNode(classDeclarationNode, newClassDeclarationNode);
syntaxTree = syntaxTree.WithRootAndOptions(newRootNode, syntaxTree.Options);//replace the original syntaxtree with new one
List<MetadataReference> assemblyReferences = GetAssemblyReferences();//implement correctly GetAssemblyReferences method
CSharpCompilation compilation = CSharpCompilation.Create(
fileName,
new[]
{
syntaxTree
}, assemblyReferences,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
MemoryStream memoryStream = new MemoryStream();
EmitResult emitResult = compilation.Emit(memoryStream);
if (!emitResult.Success)
{
List<Diagnostic> errors = emitResult.Diagnostics.ToList();
throw new AggregateException(errors.Select(er=>new Exception(er.ToString())).ToArray());
}
memoryStream.Position = 0;
return memoryStream;
}
}
}