using System.CodeDom.Compiler;
using System.Web.Razor.Parser.SyntaxTree;
using ServiceStack.Razor.ServiceStack;
using ServiceStack.Razor.Templating;
using ServiceStack.Text;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Razor;
using System.Web.Razor.Generator;
using System.Web.Razor.Parser;
namespace ServiceStack.Razor.Compilation
{
///
/// Provides a base implementation of a compiler service.
///
public abstract class CompilerServiceBase
{
private readonly CodeDomProvider CodeDomProvider;
protected CompilerServiceBase(
CodeDomProvider codeDomProvider,
RazorCodeLanguage codeLanguage, MarkupParser markupParser)
{
if (codeLanguage == null)
throw new ArgumentNullException("codeLanguage");
CodeDomProvider = codeDomProvider;
CodeLanguage = codeLanguage;
MarkupParser = markupParser ?? new HtmlMarkupParser();
}
///
/// Gets the code language.
///
public RazorCodeLanguage CodeLanguage { get; private set; }
///
/// Gets the markup parser.
///
public MarkupParser MarkupParser { get; private set; }
///
/// Builds a type name for the specified template type and model type.
///
/// The template type.
/// The model type.
/// The string type name (including namespace).
public virtual string BuildTypeName(Type templateType, Type modelType)
{
if (templateType == null)
throw new ArgumentNullException("templateType");
if (!templateType.IsGenericTypeDefinition && !templateType.IsGenericType)
return templateType.FullName;
if (modelType == null)
throw new ArgumentException("The template type is a generic defintion, and no model type has been supplied.");
bool @dynamic = CompilerServices.IsDynamicType(modelType);
Type genericType = templateType.MakeGenericType(modelType);
return BuildTypeNameInternal(genericType, @dynamic);
}
///
/// Builds a type name for the specified generic type.
///
/// The type.
/// Is the model type dynamic?
/// The string typename (including namespace and generic type parameters).
public abstract string BuildTypeNameInternal(Type type, bool isDynamic);
static string[] DuplicatedAssmebliesInMono = new string[] {
"mscorlib.dll",
"System/4.0.0.0__b77a5c561934e089/System.dll",
"System.Xml/4.0.0.0__b77a5c561934e089/System.Xml.dll",
"System.Core/4.0.0.0__b77a5c561934e089/System.Core.dll",
"Microsoft.CSharp/4.0.0.0__b03f5f7f11d50a3a/Microsoft.CSharp.dll",
};
///
/// Creates the compile results for the specified .
///
/// The type context.
/// The compiler results.
private CompilerResults Compile(TypeContext context)
{
var compileUnit = GetCodeCompileUnit(
context.ClassName,
context.TemplateContent,
context.Namespaces,
context.TemplateType,
context.ModelType);
var @params = new CompilerParameters {
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = false,
CompilerOptions = "/target:library /optimize",
};
var assemblies = CompilerServices
.GetLoadedAssemblies()
.Where(a => !a.IsDynamic)
.Select(a => a.Location)
.ToArray();
@params.ReferencedAssemblies.AddRange(assemblies);
if (Env.IsMono)
{
for (var i=@params.ReferencedAssemblies.Count-1; i>=0; i--)
{
var assembly = @params.ReferencedAssemblies[i];
foreach (var filterAssembly in DuplicatedAssmebliesInMono)
{
if (assembly.Contains(filterAssembly)) {
@params.ReferencedAssemblies.RemoveAt(i);
}
}
}
}
return CodeDomProvider.CompileAssemblyFromDom(@params, compileUnit);
}
public Type CompileType(TypeContext context)
{
var results = Compile(context);
if (results.Errors != null && results.Errors.Count > 0)
{
throw new TemplateCompilationException(results.Errors);
}
return results.CompiledAssembly.GetType("CompiledRazorTemplates.Dynamic." + context.ClassName);
}
///
/// Generates any required contructors for the specified type.
///
/// The set of constructors.
/// The code type declaration.
private static void GenerateConstructors(IEnumerable constructors, CodeTypeDeclaration codeType)
{
if (constructors == null || !constructors.Any())
return;
var existingConstructors = codeType.Members.OfType().ToArray();
foreach (var existingConstructor in existingConstructors)
codeType.Members.Remove(existingConstructor);
foreach (var constructor in constructors)
{
var ctor = new CodeConstructor { Attributes = MemberAttributes.Public };
foreach (var param in constructor.GetParameters())
{
ctor.Parameters.Add(new CodeParameterDeclarationExpression(param.ParameterType, param.Name));
ctor.BaseConstructorArgs.Add(new CodeSnippetExpression(param.Name));
}
codeType.Members.Add(ctor);
}
}
///
/// Gets the code compile unit used to compile a type.
///
/// The class name.
/// The template to compile.
/// The set of namespace imports.
/// The template type.
/// The model type.
/// A used to compile a type.
public CodeCompileUnit GetCodeCompileUnit(string className, string template, ISet namespaceImports, Type templateType, Type modelType)
{
if (string.IsNullOrEmpty(className))
throw new ArgumentException("Class name is required.");
if (string.IsNullOrEmpty(template))
throw new ArgumentException("Template is required.");
templateType = templateType
?? ((modelType == null)
? typeof(TemplateBase)
: typeof(TemplateBase<>));
var host = new MvcWebPageRazorHost(CodeLanguage, () => MarkupParser) {
DefaultBaseClass = BuildTypeName(templateType, modelType),
DefaultClassName = className,
DefaultNamespace = "CompiledRazorTemplates.Dynamic",
GeneratedClassContext = new GeneratedClassContext(
"Execute", "Write", "WriteLiteral",
"WriteTo", "WriteLiteralTo",
"ServiceStack.Razor.Templating.TemplateWriter",
"WriteSection")
};
var templateNamespaces = templateType.GetCustomAttributes(typeof(RequireNamespacesAttribute), true)
.Cast()
.SelectMany(att => att.Namespaces);
foreach (string ns in templateNamespaces)
namespaceImports.Add(ns);
foreach (string @namespace in namespaceImports)
host.NamespaceImports.Add(@namespace);
var engine = new RazorTemplateEngine(host);
GeneratorResults result;
using (var reader = new StringReader(template))
{
result = engine.GenerateCode(reader);
}
var type = result.GeneratedCode.Namespaces[0].Types[0];
if (modelType != null)
{
if (CompilerServices.IsAnonymousType(modelType))
{
type.CustomAttributes.Add(new CodeAttributeDeclaration(
new CodeTypeReference(typeof(HasDynamicModelAttribute))));
}
}
GenerateConstructors(CompilerServices.GetConstructors(templateType), type);
var statement = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "Clear");
foreach (CodeTypeMember member in type.Members)
{
if (member.Name.Equals("Execute"))
{
((CodeMemberMethod)member).Statements.Insert(0, new CodeExpressionStatement(statement));
break;
}
}
return result.GeneratedCode;
}
public IEnumerable AllNodesOfType(Block block)
{
if (block is T)
yield return (T)(object)block;
foreach (var syntaxTreeNode in block.Children)
{
if (syntaxTreeNode is T)
yield return (T)(object)syntaxTreeNode;
var childBlock = syntaxTreeNode as Block;
if (childBlock == null) continue;
foreach (var variable in AllNodesOfType(childBlock))
{
yield return variable;
}
}
}
}
}