diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index a2de7db8c..d12c32e1b 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -28,16 +28,34 @@ open System open System.IO open System.Dynamic open System.Collections.Generic +open System.Collections.Concurrent open RazorEngine open RazorEngine.Text open RazorEngine.Templating open RazorEngine.Configuration -type RazorRender(layoutRoots, namespaces) = - // Create resolver & set it to the global static filed +type GetMemberBinderImpl (name) = + inherit GetMemberBinder(name, false) + let notImpl () = raise <| new NotImplementedException() + override x.FallbackGetMember(v, sug) = notImpl() + +type RazorRender(layoutRoots, namespaces, templateName:string, ?model_type:System.Type) = + let templateName = + if templateName.EndsWith(".cshtml") then + templateName.Substring(0, templateName.Length - 7) + else templateName + let templateCache = new ConcurrentDictionary() + // Create resolver & set it to the global static field let templateResolver = { new ITemplateResolver with - member x.Resolve name = File.ReadAllText(RazorRender.Resolve(layoutRoots, name + ".cshtml")) } + member x.Resolve name = + templateCache.GetOrAdd (name, fun name -> + match RazorRender.Resolve(layoutRoots, name + ".cshtml") with + | Some file -> File.ReadAllText(file) + | None -> + failwith "Could not find template file: %s\nSearching in: %A" name layoutRoots + null) + } do RazorRender.Resolver <- templateResolver // Configure templating engine @@ -50,31 +68,9 @@ type RazorRender(layoutRoots, namespaces) = let templateservice = new TemplateService(config) do Razor.SetTemplateService(templateservice) - /// Global resolver (for use in 'DocPageTempalateBase') - static member val Resolver = null with get, set - /// Find file in one of the specified layout roots - static member Resolve(layoutRoots, name) = - let partFileOpt = - layoutRoots |> Seq.tryPick (fun layoutRoot -> - let partFile = Path.Combine(layoutRoot, name) - if File.Exists(partFile) then Some partFile else None) - match partFileOpt with - | None -> failwithf "Could not find template file: %s\nSearching in: %A" name layoutRoots - | Some partFile -> partFile - - /// Model - whatever the user specifies for the page - member val Model : obj = obj() with get, set - /// Dynamic object with more properties (?) - member val ViewBag = new DynamicViewBag() with get,set - - /// Process source file and return result as a string - member x.ProcessFile(source, ?properties) = + let handleCompile source f = try - x.ViewBag <- new DynamicViewBag() - for k, v in defaultArg properties [] do - x.ViewBag.AddValue(k, v) - let html = Razor.Parse(File.ReadAllText(source), x.Model, x.ViewBag, source) - html + f () with | :? TemplateCompilationException as ex -> let csharp = Path.GetTempFileName() + ".cs" @@ -83,11 +79,75 @@ type RazorRender(layoutRoots, namespaces) = use _c = Log.colored ConsoleColor.Red printfn "\nProcessing the file '%s' failed\nSource written to: '%s'\nCompilation errors:" source csharp for error in ex.Errors do - printfn " - (%d, %d) %s" error.Line error.Column error.ErrorText + let errorType = if error.IsWarning then "warning" else "error" + printfn " - %s: (%d, %d) %s" errorType error.Line error.Column error.ErrorText printfn "" ) + Log.close() // wait for the message to be printed completly failwith "Generating HTML failed." + let withProperties properties (oldViewbag:DynamicViewBag) = + let viewBag = new DynamicViewBag() + // TODO: use new DynamicViewBag(oldViewbag) and remove GetMemberBinderImpl + for old in oldViewbag.GetDynamicMemberNames() do + match oldViewbag.TryGetMember(new GetMemberBinderImpl(old)) with + | true, v -> viewBag.AddValue(old, v) + | _ -> () + for k, v in defaultArg properties [] do + viewBag.AddValue(k, v) + viewBag + + /// Global resolver (for use in 'DocPageTempalateBase') + static member val Resolver = null with get, set + /// Find file in one of the specified layout roots + static member Resolve(layoutRoots, name) = + layoutRoots |> Seq.tryPick (fun layoutRoot -> + let partFile = Path.Combine(layoutRoot, name) + if File.Exists(partFile) then Some partFile else None) + static member ForceResolve(layoutRoots, name) = + match RazorRender.Resolve(layoutRoots, name) with + | Some f -> f + | None -> + failwith "Could not find template file: %s\nSearching in: %A" name layoutRoots + static member Run<'m>(name, model:'m, viewBag:DynamicViewBag) = + if Razor.Resolve<'m>(name, model) = null then + let templateContent = RazorRender.Resolver.Resolve(name) + Razor.Compile(templateContent, (if obj.ReferenceEquals(model, null) then typeof<'m> else model.GetType()), name) + Razor.Run<'m>(name, model, viewBag) + + member internal x.HandleCompile source f = handleCompile source f + member internal x.TemplateName = templateName + member internal x.WithProperties properties = withProperties properties x.ViewBag + + /// Dynamic object with more properties (?) + member val ViewBag = new DynamicViewBag() with get, set + member x.ProcessFile(?properties) = + handleCompile templateName (fun _ -> + RazorRender.Run(templateName, null, x.WithProperties properties)) + /// Process source file and return result as a string + member x.ProcessFileParse(?properties) = + handleCompile templateName (fun _ -> + Razor.Parse(templateResolver.Resolve templateName, null, x.WithProperties properties, templateName)) + + member x.ProcessFileModel(model:obj,?properties) = + handleCompile templateName (fun _ -> + RazorRender.Run(templateName, model, x.WithProperties properties)) + /// Process source file and return result as a string + member x.ProcessFileParseModel(model:obj, ?properties) = + handleCompile templateName (fun _ -> + Razor.Parse(templateResolver.Resolve templateName, model, x.WithProperties properties, templateName)) + +and RazorRender<'model>(layoutRoots, namespaces, templateName) = + inherit RazorRender(layoutRoots, namespaces, templateName, typeof<'model>) + + member x.ProcessFile(model:'model, ?properties) = + x.HandleCompile x.TemplateName (fun _ -> + RazorRender.Run<'model>(x.TemplateName, model, x.WithProperties properties)) + /// Process source file and return result as a string + member x.ProcessFileParse(model:'model, ?properties) = + x.HandleCompile x.TemplateName (fun _ -> + Razor.Parse<'model>(RazorRender.Resolver.Resolve x.TemplateName, model, x.WithProperties properties, x.TemplateName)) + and StringDictionary(dict:IDictionary) = member x.Dictionary = dict /// Report more useful errors when key not found (.NET dictionary does not do this...) @@ -134,7 +194,9 @@ and [] DocPageTemplateBase<'T>() = with get() = StringDictionary(defaultArg (x.tryGetViewBagValue> "Properties") (dict [])) and set (value:StringDictionary) = x.trySetViewBagValue> "Properties" value.Dictionary - member x.Root = x.Properties.["root"] - + member x.Root = x.Properties.["root"] member x.RenderPart(name, model:obj) = - Razor.Parse(RazorRender.Resolver.Resolve(name), model) + if Razor.Resolve(name, model) = null then + let templateContent = RazorRender.Resolver.Resolve(name) + Razor.Compile(templateContent, model.GetType(), name) + Razor.Run(name, model) diff --git a/src/FSharp.Literate/Formatting.fs b/src/FSharp.Literate/Formatting.fs index d1c56034b..9570893e1 100644 --- a/src/FSharp.Literate/Formatting.fs +++ b/src/FSharp.Literate/Formatting.fs @@ -73,9 +73,9 @@ module Templating = let private generateFile contentTag parameters templateOpt output layoutRoots = match templateOpt with | Some (file:string) when file.EndsWith("cshtml", true, CultureInfo.InvariantCulture) -> - let razor = RazorRender(layoutRoots, []) + let razor = RazorRender(layoutRoots, [], Path.GetFileNameWithoutExtension file) let props = [ "Properties", dict parameters ] - let generated = razor.ProcessFile(file, props) + let generated = razor.ProcessFile(props) File.WriteAllText(output, generated) | _ -> let templateOpt = templateOpt |> Option.map File.ReadAllText diff --git a/src/FSharp.MetadataFormat/Main.fs b/src/FSharp.MetadataFormat/Main.fs index a49004a14..deeede1a1 100644 --- a/src/FSharp.MetadataFormat/Main.fs +++ b/src/FSharp.MetadataFormat/Main.fs @@ -883,11 +883,10 @@ type MetadataFormat = // Generate all the HTML stuff Log.logf "Starting razor engine" - let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"]) - razor.Model <- box asm + let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"], namespaceTemplate) Log.logf "Generating: index.html" - let out = razor.ProcessFile(RazorRender.Resolve(layoutRoots, namespaceTemplate), props) + let out = razor.ProcessFile(asm, props) File.WriteAllText(outDir @@ "index.html", out) // Generate documentation for all modules @@ -899,14 +898,13 @@ type MetadataFormat = [ for ns in asm.Namespaces do for n in ns.Modules do yield! nestedModules n ] - let moduleTemplateFile = RazorRender.Resolve(layoutRoots, moduleTemplate) - Parallel.pfor modules (fun () -> RazorRender(layoutRoots,["FSharp.MetadataFormat"])) (fun modul _ razor -> + let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"], moduleTemplate) + + for modul in modules do Log.logf "Generating module: %s" modul.UrlName - razor.Model <- box (ModuleInfo.Create(modul, asm)) - let out = razor.ProcessFile(moduleTemplateFile, props) + let out = razor.ProcessFile(ModuleInfo.Create(modul, asm), props) File.WriteAllText(outDir @@ (modul.UrlName + ".html"), out) Log.logf "Finished module: %s" modul.UrlName - razor) Log.logf "Generating types..." let rec nestedTypes (modul:Module) = seq { @@ -918,11 +916,9 @@ type MetadataFormat = yield! ns.Types ] // Generate documentation for all types - let typeTemplateFile = RazorRender.Resolve(layoutRoots, typeTemplate) - Parallel.pfor types (fun () -> RazorRender(layoutRoots, ["FSharp.MetadataFormat"])) (fun typ _ razor -> + let razor = new RazorRender(layoutRoots, ["FSharp.MetadataFormat"], typeTemplate) + for typ in types do Log.logf "Generating type: %s" typ.UrlName - razor.Model <- box (TypeInfo.Create(typ, asm)) - let out = razor.ProcessFile(typeTemplateFile, props) + let out = razor.ProcessFile(TypeInfo.Create(typ, asm), props) File.WriteAllText(outDir @@ (typ.UrlName + ".html"), out) Log.logf "Finished type: %s" typ.UrlName - razor)