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; } } } } }