Code Monkey home page Code Monkey logo

westwind.razorhosting's Introduction

Westwind.RazorHosting

Hosting the Razor Runtime outside of ASP.NET MVC/WebPages

This library wraps the .NET Razor engine for use as a standalone template engine outside of ASP.NET MVC. Using this library you can host the Razor engine in desktop, console, service or any kind of full framework .NET application and render pages from strings or the file system.

Features

  • Standalone Razor Template Rendering
  • Use in desktop, console, service or even Web applications
  • No dependencies on ASP.NET or MVC
  • Render templates from string content
  • Render templates from a folder structure
  • Support for Partial Pages and Layout Pages (FolderHostContainer)
  • Provides HostContainers for template caching and change detection
  • Support for execution in separate AppDomain

Use Cases

  • Creating HTML output in desktop applications
  • Creating text merge documents: Email confirmations, merge letters etc.
  • Creating HTML reports
  • Code and template generation
  • Dynamic code execution (Razor Templates can execute arbitrary code after all)

More Information

Similar to MVC Razor but not MVC Razor!

The RazorHosting engine provides core templating functionality of the raw Razor sytnax engine. This means that all C# language features and all of Razor's basic expression and logic parsing features work.

It does not provide full parity with either the MVC or WebPages implementations however, since both of these engines are closely tied to ASP.NET semantics. Things like HTML and URL Helpers, Sections, Partials and Layout pages are not natively supported by Razor. Partials, Helpers and Layout pages are supported only in the RazorFolderHost implementation of this library.

RazorHosting only supports C# - there's no support for Visual Basic.

Installation

The easiest way to install is via NuGet:

install-package westwind.razorhosting

The package requires .NET 4.5 or later.

Basic Concepts

This Razor Host library provides two distinct hosting models:

  • Raw Razor Engine
    A very easy to use string template function. Useful for very simple one off template rendering from strings, but not recommended for repeated rendering of the same templates.

  • Razor Host Containers
    A wrapper around the base Razor Engine that provides cached templates, serving templates from a folder hierarchy (like ASP.NET) and optionally loading templates in a separate AppDomain. The RazorFolderHostContainer container provides support for Partials and Layout pages.

For real world applications you almost always want to use a Host Container.

Simple RazorEngine Usage

RazorEngine is the base template parsing engine. It's very easy to use but provides no internal caching or fixup of templates although you can manually manage this easily.

To execute a template:

string template = @"Hello World @Model.Name. Time is: @DateTime.Now";
var host = new RazorEngine();
string result = host.RenderTemplate(template,new { Name="Joe Doe" });

You can also create a template, compile it and then cache it so you can reuse it later:

string template = @"Hello World @Model.Name. Time is: @DateTime.Now";
var host = new RazorEngine<RazorTemplateBase>();
host.AddAssembly("System.Data.dll");  // add any assemblies you need in templates            
    
string compiledId = host.CompileTemplate(template);    
string result = host.RenderTemplateFromAssembly(compiledId,
                            new Person() { Name = "Joe Doe" });
...

// Run again later without recompilation
string result = host.RenderTemplateFromAssembly(compiledId,
                            new Person() { Name = "Rick Strahl" });

The latter example lets you capture a compilation id which points to a cached template assembly in the current RazorEngine instance. Running an already compiled template is considerably faster and saves resources as no new assembly is created each time you run the same template.

Caching is Important

Razor compiles every template into code and compiles the template into an assembly. Without caching, templates are constantly recreated and new assemblies are created which wastes resources. Since assemblies can't unload this basically means you have a memory leak. Cache your templates either as described above, or use a host container which automatically cache and detect changes templates in temlates.

Model Data

All templates include a Model property and the RenderTemplate() has a model parameter that you can pass to the template. By default models are of type dynamic, but the model can also be explicitly typed by using the Razor @inherits tag:

@inherits RazorTemplateBase<RazorHostingTests.Person>
<h3>Hello @Model.Firstname.</h3>

you can also use the more familiar @model tag:

@model RazorHostingTests.Person
<h3>Hello @Model.Firstname.</h3>

but you'll sacrifice IntelliSense in Visual Studio - IntelliSense only works with the @inherits syntax. If no @model or @inherits is specified, the Model is assumed to be of type dynamic.

Using Host Containers

Host Containers wrap the basic RazorEngine by providing automatic caching for templates, template change detection and the ability to optionally run the Razor templates in a separate AppDomain.

There are two provided HostContainers that address the most common use cases:

  • RazorStringHostContainer
    Renders templates from strings. Templates are cached based on the template's text.

  • RazorFolderHostContainer
    Renders templates from the file system by pointing at a template on disk. Templates are cached based on file timestamps. Folder hosted templates support Partial Layout and Layout pages.

HostContainers are meant to be reused, so you typically instantiate it once, then hang on to the reference and reuse it for subsequent requests. The template cache is associated with an instance so in order to get the caching benefit the instance needs to stay alive.

A better Approach - Host Container and Application Wrapper

Before we look closer at HostContainers, here's a recommended approach for creating an application ready handler that makes it easy to render templates with a single line of code. I like to wrap all template rendering logic into an application specific static class that cachese the host container.

The following uses the RazorFolderHostContainer which uses disk based templates out of a root folder. Here's what this small class looks like:

public class AppTemplates
{
    public static RazorFolderHostContainer RazorHost { get; set; }
    
    public static string RenderTemplate(string template, object model, out string error)
    {
        if (RazorHost == null)
            StartRazorHost();        
        
        string result = RazorHost.RenderTemplate(template,model);
        if (result == null)
            error = RazorHost.ErrorMessage;
            
        return result;
    }
    
    public static void StartRazorHost()
    {
        var host = new RazorFolderHostContainer() 
        {
            // *** Set your Folder Path here - physical or relative ***
            TemplatePath = Path.GetFullPath(@".\templates\"),
            // *** Path to the Assembly path of your application
            BaseBinaryFolder = Environment.CurrentDirectory
        };
        
        // Add any assemblies that are referenced in your templates
        host.AddAssemblyFromType(typeof(Person));
        host.AddAssemblyFromType(typeof(AppConfiguration));
        
  
        // Always must start the host
        host.Start();
        
        RazorHost = host;
    }
    
    public static void StopRazorHost()
    {
        RazorHost?.Stop();
    }
}

This consolidates all the logic needed to load, shut down and render templates using the RazorHost. Now you can use a single line to render any template out of the folder structure.

AppTemplates.RenderTemplate("~/header.cshtml",topic, out string error);

This method will start the host if it's not loaded and then render the template.

The same approach can be used with the StringHostContainer with slightly different configuration.

RazorStringHostContainer

StringHostContainer executes templates from string, but caches the compiled templates based on the template's content. IOW, running the same exact template twice will automatically compile on the first run, and use the cached version on the second and subsequent runs. As long as the the template string is identical the cached assembly is used.

To run a String Template Host:

var host = new RazorStringHostContainer();
//host.UseAppDomain = true; 
        
// add model assembly - ie. this assembly
host.AddAssemblyFromType(this);
host.AddAssembly("System.Data.dll");
    
// must start the host container
host.Start();
              
// Create a model to pass in
Person person = new Person()
{
    Name = "Rick",
    Company = "West Wind",
    Entered = DateTime.Now,
    Address = new Address()
    {
        Street = "32 Kaiea",
        City = "Paia"
    }
};

// create a template to render
string template = @"@inherits Westwind.RazorTemplateBase<RazorHostingTests.Person>
<b>@Model.Name of @Model.Company entered on @Model.Entered";
    
// Render the actual template and pass the model
string result = host.RenderTemplate(string,person);
    	
Console.WriteLine(result);
Console.WriteLine("---");
Console.WriteLine(host.Engine.LastGeneratedCode);

if (result == null)
    Assert.Fail(host.ErrorMessage);

// shut down the host            
host.Stop();

// Hosts also implement IDisposable to Stop
host.Dispose();  

With a host container you typically will run many requests between the Start() and Stop() operations.

RazorFolderHostContainer

The RazorFolderHostContainer can be used to point to a folder on disk and treat it like a virtual directory for rendering templates from disk. Templates are loaded based on a virtual path (~/page.cshtml and ~/sub/page.cshtml) relative to the base folder, and support usage for subpages via @RenderPartial() and layout pages via the Layout property. The template loading behavior is similar to ASP.NET MVC's path based View handling.

To run folder host templates:

var host = new RazorFolderHostContainer();

// must specify the base path ('Virtual' root path) for templates
host.TemplatePath = Path.GetFullPath("..\\..\\FileTemplates\\");

// point at the folder where dependent assemblies can be found
// this applies only to separate AppDomain hosting
host.BaseBinaryFolder = Environment.CurrentDirectory;

// add model assembly - ie. this assembly
host.AddAssemblyFromType(typeof(Person));

host.UseAppDomain = true;
//host.Configuration.CompileToMemory = true;
//host.Configuration.TempAssemblyPath = Environment.CurrentDirectory;

// Always must start the host
host.Start();

// create a model to pass
Person person = new Person()
{
	Name = "Rick",
	Company = "West Wind",
	Entered = DateTime.Now,
	Address = new Address()
	{
		Street = "32 Kaiea",
		City = "Paia"
	}
};

// render a template and pass the model
string result = host.RenderTemplate("~/HelloWorld.cshtml", person);

Console.WriteLine(result);
Console.WriteLine("---");
Console.WriteLine(host.Engine.LastGeneratedCode);

host.Stop();

if (result == null)
	Assert.Fail(host.ErrorMessage);

Assert.IsTrue(result.Contains("West Wind"));

where the template might look like this:

@inherits RazorTemplateFolderHost<RazorHostingTests.Person>
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<title></title>
</head>
<body>    
    @RenderPartial("~/Header_Partial.cshtml",Model)

	@Model.Name @Model.Company @Model.Address.City @Model.Entered 
    
	@{for (int i = 0; i < 10; i++)
		{
			Response.WriteLine(i + ".");
		}  

	You can also render nested templates from string
	@RenderTemplate("Child Template rendered from String. Name: @Model.Name",Model) 
</body>
</html>

Rendering Layout Pages

You can also use Layout pages with the RazorFolderHostContainer by specifying the Layout property in your view/template. Layout pages are like master pages that render around a content page and can be reused to provide a common shell. Layout pages in turn can also contain template tags and call out to Partial pages.

To specify a Layout page, set the Layout property in your code.

@inherits Westwind.RazorHosting.RazorTemplateFolderHost<RazorHostingTests.Person>
@{
    Layout = "~/_Layout.cshtml"
}
<h3>Hello @Model.Firstname</h3>
<p>
this is my page content rendered at @DateTime.Now.
</p>

The View Page then:

@inherits Westwind.RazorHosting.RazorTemplateFolderHost<RazorHostingTests.Person>

<h1>Layout Page Header</h1>
<hr />

@RenderBody()

<hr/>
&copy; West Wind Technologies, [email protected]

Note that you should use the same model your are passing to the content page as a parameter in the layout page - if you plan on accessing model content.

Error Handling in Host Containers

Host containers tend to render to string values and will return null on error. You can also check the LastException property for non null to check for the presence of an error.

The following demonstrates typical host error logic:

string result = host.RenderTemplate("~/RuntimeError.cshtml", person);

Assert.IsNull(result, "result should be null on error");
Console.WriteLine(host.ErrorMessage);  // simple message

Assert.IsNotNull(host.LastException, "Last exception should be set");

Console.WriteLine("---"); 
Console.WriteLine(host.LastException.Message);
Console.WriteLine(host.LastException.ActiveTemplate); // only in RazorFolderHost
//Console.WriteLine(host.LastException.GeneratedSourceCode);
Console.WriteLine("---");

// Render HTML output of the error
// For RazorFolderHost also renders the template source code and line numbers
Console.WriteLine(host.RenderHtmlErrorPage());

Html Helpers

You can also use HTML Helpers in your Razor views:

@helper WriteBlockText(string text)
{
    <b>*** @text ***</b>
}

Helper output: @WriteBlockText("Help me!");

Running in a separate AppDomain with UseAppDomain

Note that you can choose to host the container in a separate AppDomain by using:

host.UseAppDomain = true;

If you do, make sure any models passed into the host for rendering are marked [Serializable] or inherit from MarshalByRefObject. Due to these limitations you have to ensure that all parameters and dependent members can be serialized when passing parameters from your application to the RazorEngine via AppDomains.

Using an AppDomain is useful when loading lots of templates and allows for unloading the engine to reduce memory usage. It also helps isolate template code from the rest of your application for security purposes, since Razor templates essentially can execute any code in the context of the host.

Caching the Host Container in a Static or Singleton

The easiest way to cache a host container is via static variable declared in a relevant location of your application.

For example you can do this:

public class AppUtils 
{
    public static RazorFolderHostContainer RazorHost { get; set; }
    ...
}

and then to use it:

RazorFolderHostContainer host;

if (AppUtils.RazorHost == null)
{
    var host = new RazorFolderHostContainer();
    // ... set up the host
    AppUtils.RazorHost = host;
}

AppUtils.RenderTemplate("~/topic.cshtml",topic);

Put it all together: Application Integration

I like to wrap all template rendering logic into an application specific static class so I cache the host container and not have to worry about loading the container.

Here's what this small class looks like:

public class AppTemplates
{
    public static RazorFolderHostContainer RazorHost { get; set; }
    
    public static string RenderTemplate(string template, object model, out error)
    {
        if (RazorHost == null)
            StartRazorHost();        
        
        string result = RazorHost.RenderTemplate(template,model);
        if (result == null)
            error = RazorHost.ErrorMessage;
            
        return result;
    }
    
    public static void StartRazorHost()
    {
        var host = new RazorFolderHostContainer() 
        {
            // *** Set your Folder Path here - physical or relative ***
            TemplatePath = Path.GetFullPath(@".\templates\"),
            BaseBinaryFolder = Environment.CurrentDirectory
        };
        host.AddAssemblyFromType(typeof(Person));
        host.AddAssemblyFromType(this.GetType());
  
        // Always must start the host
        host.Start();
        
        RazorHost = host;
    }
    
    public static void StopRazorHost()
    {
        RazorHost?.Stop();
    }
}

This consolidates all the logic needed to load, shut down and render templates using the RazorHost. This reduces the code to render a template to a single line of code now:

AppTemplates.RenderTemplate("~/header.cshtml",topic);

This method will start the host if it's not loaded and then go ahead and render the template.

Using Latest C# Language Features

The library by default uses the .NET Runtime built in CSharp code provider which supports only C# 5.x. It's possible to use later versions of C# by explicitly adding the new .NET Compiler platform libraries to the host project and explicitly providing the CSharpProvider. We don't provide this in the box because the Roslyn compiler package is very large (17megs) so we provide an opt-in mechanism that lets you decide if you need the newer language features and extra deployment requirements for your host application.

To add C# 7.x support:

  • Add Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider Nuget Package to your Project
  • Add <system.codedom> section to host app's app.config
  • Provide CSharpCodeProvider to RazorEngine or HostContainer

Here are the required app.config settings that ensures that the compiler binaries can be found:

<configuration>
	<appSettings>
		<add key="aspnet:RoslynCompilerLocation" value="roslyn"/>
	</appSettings>
	<system.codedom>
		<compilers>
			<compiler language="c#;cs;csharp" extension=".cs"
				type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
				warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/>
			<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
				type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
				warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+"/>
		</compilers>
	</system.codedom>
</configuration>

To use the compiler with your Razor code use one of the following approaches to pass the CSharpCodeProvider:

Basic RazorEngine
var cs = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider()
var engine = new RazorEngine(cs);
Using a HostContainer
var host = new RazorFolderHostContainer();
host.CodeProvider = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider();

Does not work with Out of Process Hosting

This approach of providing the CSharp code provider does not work with AppDomain loading of the Razor engine in host containers. For AppDomain loaded instances only the stock C# 5 provider is used.

License

This library is published under MIT license terms:

Copyright Β© 2012-2019 Rick Strahl, West Wind Technologies

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Give back

westwind.razorhosting's People

Contributors

rickstrahl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

westwind.razorhosting's Issues

RenderTemplate returns null when there are media queries in template

I template I used is

<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>New Ticket</title>
<style media="all" type="text/css">
    @media all {
      .btn-secondary a:hover {
        border-color: #34495e !important;
        color: #34495e !important;
      }
    }
    </style>
<body>Hello world</body>
  </head>
</html>

Misleading error when model types do not match

When one writes a template using a specific model type (not dynamic) and the template is rendered with a different model type, the rendering fails without an exception. The only hint is a misleading text in RazorEngine.ErrorMessage saying

Rendering failed: Template Execution Error: Object reference not set to an instance of an object.

The reason for this is IMO a wrong try ... catch block in RazorEngine, which catches the precise exception and ignores it.

try
{
    dynamic dynInstance = instance;
    dynamic dcontext = context;
    dynInstance.Model = dcontext;
}
catch(Exception ex) 
{
    var msg = ex.Message;
}

msg is not used anymore although it would contain the perfect text

Cannot implicitly convert type 'A' to 'B'. An explicit conversion exists (are you missing a cast?)

I suggest to remove this try ... catch block as the outer try ... catch will do the exception handling the way I would expect.

Throw on error

The interface doesn't throw in the event of errors. The caller must know that errors will be available in host.ErrorMessage.

I'd like a feature that allows me to set a property such as ThrowOnError to true so that the call will throw if there is an error rendering the template.

Thread safety of RazorEngine.ErrorMessage

Hi, if we have an application that is using any derived type from RazorTemplateBase, and using the application wrapper as described in the documentation (link to doc), for instance, if there would be a case where two threads try generating output based on a template, with different model data, if one thread for instance fails, and the other passes, that might result in a case where the ErrorMessage string would potentially be overwritten to null for the thread that failed, or it would have been overwritten with incorrect error message in the thread that succeeded in making a valid response? And then when accessing ErrorMessage we might not be able to access the reason why template generation failed.

This is all theoretical and I haven't observed such a a situation when using this library so far.

What would you suggest to be able to avoid such thread safety issues that might occur?
I am thinking of two things:

  • Either using all the HostContainers in a Object pool
  • Or maybe wrapping ErrorMessage in a lock statement?

In any case, thanks for any feedback provided.

RazorFolderHostContainer BaseBinaryFolder wrong assumption

The RazorFolderHostContainer has a property BaseBinaryFolder that is initialized to Environment.CurrentDirectory.

When running as a Windows Service that means that the base folder for assembly probing will be C:\Windows\System32 thus assembly lookup always fails.

The workaround is to manually set that property to the correct bin path, but could be interesting to change the default value to:

Assembly.GetEntryAssembly().Location

temp files

Is there any way to delete generated temporary files?

Support for newer C# language features, e.g. string interpolation

It's possible to user a newer C# compiler in a project by adding the appropriate nuget package. For example this adds string interpolation.

Rendering a template that uses newer C# language features fails with error messages such as

"Line: 29, Column: 18, Error: Unexpected character '$'Line: 33, Column: 27, Error: Unexpected character '$'"

Is there a way to instruct Westwind.RazorHosting to use a particular C# compiler?

Usage with ASP WebApi

Hello, I'm trying to use this library for email templating in ASP web Api, I'm using the UseAppdomain Option, but is unable to load the Westwind.Razor dll, I have set the BaseBinaryFolder to the path where all the dll's exists, but the error is the same, if I use the UseAppdomain = false, everything works fine, I tried this with a console application, and works with no issue

Web Api Structure (root path)
c:\...\
imagen

this is the class for the renderer I'm Using

public class RazorTemplates : IDisposable
    {
        RazorFolderHostContainer Host { get; set; }

        public RazorTemplates()
        {
            string ServerMapPath = HttpContext.Current.Server.MapPath("~"); // c:\\...\ root path

            Host = new RazorFolderHostContainer()
            {
                // *** Set your Folder Path here - physical or relative ***
                TemplatePath = Path.Combine(ServerMapPath, "Views", "Email"),//c:\\...\Views\Email
                // *** Path to the Assembly path of your application
                BaseBinaryFolder = Path.Combine(ServerMapPath, "bin"), //c:\\...\bin
                UseAppDomain = true,
                ThrowExceptions = true
            };

            Host.Start();
        }

        public string RenderTemplate(string template, object model)
        {
            string result = Host.RenderTemplate(template, model);
            if (result == null)
            {
                throw new InvalidOperationException(Host.ErrorMessage);
            }
            return result;
        }

        public void Dispose()
        {
            Host.Stop();
            Host.Dispose();
        }
    }

Rendering a template

            using (RazorTemplates host = new RazorTemplates())
            {
                return Ok(host.RenderTemplate("~/_PartialPage1.cshtml", new { }));
            }

The exception:

imagen

RenderPage nor RenderPartial works

When I try to include another CSTHML I get
'The name 'RenderPage' does not exist in the current context' error.
Any idea how to fix it?
Version 3.3.9

Add Css File

Hi,

Is it possible to add CSS file? I tried to add the CSS file with @Url.Content but it gives the following error:

Failed to render Layout Page: Line: 43, Column: 81, Error: The name 'ResolveUrl' does not exist in the current context

<link rel="stylesheet" href="@Url.Content("~/Content/EmailTemplate.css")" type="text/css" />

Thanks,
Attiqe

Class AppTemplates in landing page doesn't compile

Landing page at https://github.com/RickStrahl/Westwind.RazorHosting contains an example class AppTamplate with a method named RenderTemplate()

I receive a compile error in my environment. It appears as if the parameter error is missing a type. Therefore I changed the signature to the following:

public static string RenderTemplate(string template, object model, out string error)

and as a consequence added the following as a first line in the method:

error = string.Empty;

And - using latest C# syntax - the call using this class should then be changed to

AppTemplates.RenderTemplate("~/header.cshtml", topic, out string error);

Admittedly the class is only meant to give a starting point. I think, though, that it should be syntactically correct. Perhaps I'm overlooking something, though.

Razor Hosting inside asp.net

Hello:

I have a problem using RazorHosting inside asp.net app.
Could not load the assembly RazorHosting.

You can help us?
Thanks.

Rendering view with a layout and a partial view results in the layout not being rendered

If I have a view that is rendered inside of a layout and also has a partial view within the main view, the layout never gets rendered. Here's the simplest example I've tested.

TestLayout.cshtml:

This is the test layout.
@RenderBody()

Test.cshtml:

@{ 
    Layout = "~/Storage/EmailTemplates/TestLayout.cshtml";
}
This is the main view.
@RenderPartial("~/Storage/EmailTemplates/TestPartial.cshtml", Model)

TestPartial.cshtml:
This is the test partial.

Output:

This is the main view.
This is the test partial.

If I remove the @RenderPartial call, this is the output I get:

This is the test layout.

This is the main view.

It appears that as soon as I add the partial view, the layout stops rendering.

Other information:
Westwind.RazorHosting v3.3.0 from NuGet
RazorHost initialization:

                    razorHost = new RazorFolderHostContainer()
                    {
                        TemplatePath = AppDomain.CurrentDomain.BaseDirectory,
                        ThrowExceptions = true
                    };

                    // ... calls to AddAssemblyFromType(...)

                    razorHost.Start();

Call to render template:
RazorHost.RenderTemplate("Storage/EmailTemplates/Test.cshtml", new object());
.NET Framework 4.6.2 Console Application
Visual Studio 2017 Enterprise (15.8.5)
Windows 10 (1803, Build 17134.48)

There is a error when processing a template with LF line separator.

Hello!
There is a error when processing a template with LF line separator (\n instead of \r\n ).

Method RazorEngine.FixupTemplate can be changed:

            int at = template.IndexOf("@model ");
            int at2 = template.IndexOf("\r\n", at);
            // Added by me
            if (at2 == -1)
            {
                at2 = template.IndexOf("\n", at);
            }

Getting System.PlatformNotSupportedException and Unable to use on .net core Console application

Hi,
I have tried to use both RazorFolderHostContainer and RazorStringHostContainer but it's not working.
I am getting the following error:

Message: Operation is not supported on this platform.
StackTrace: at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch(CompilerParameters options, String[] fileNames)
at Microsoft.CSharp.CSharpCodeGenerator.FromDomBatch(CompilerParameters options, CodeCompileUnit[] ea)
at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromDomBatch(CompilerParameters options, CodeCompileUnit[] ea)
at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromDom(CompilerParameters options, CodeCompileUnit[] compilationUnits)
at Westwind.RazorHosting.RazorEngine1.CompileTemplate(TextReader templateReader, String generatedNamespace, String generatedClassName) at Westwind.RazorHosting.RazorEngine1.CompileTemplate(String templateText, String generatedNamespace, String generatedClassName)
at Westwind.RazorHosting.RazorFolderHostContainer1.GetAssemblyFromFileAndCache(String relativePath) at Westwind.RazorHosting.RazorFolderHostContainer1.RenderTemplate(String relativePath, Object model, TextWriter writer, Boolean isLayoutPage)

Does it not support .net core?

Thanks in advance.

.html instead of .cshtml for partials

Hi,

Many thanks for this tool, it's awesome.

Is there any way to use .html instead of .cshtml for partials? (when I use @RenderPartial("~/header.cshtml", Model) everything works fine, but not for @RenderPartial("~/header.html", Model), RenderTemplate returns null)

I used your AppTemplates example, btw.

Mehdi

reference needed for Html helpers?

I am using a static RazorFolderHostContainer host and passing my model into a template from disk. When I call RenderTemplate I get these errors:

  • 'Westwind.RazorHosting.HtmlHelper' does not contain a definition for 'DisplayFor' and no extension method 'DisplayFor' accepting a first argument of type 'Westwind.RazorHosting.HtmlHelper' could be found (are you missing a using directive or an assembly reference?)

I was under the impression that DisplayFor would work according to this from the Readme.md:

Partials, Helpers and Layout pages are supported only in the RazorFolderHost implementation of this library.

I installed the MVC NuGet and added this line to bring in the class that holds the DisplayFor method, but the error persists:

host.AddAssemblyFromType(typeof(System.Web.Mvc.Html.DisplayExtensions));

Generated source files should use fully qualified names for Westwind classes to avoid compile errors

When Westwind.RazorHost creates a C# file, it includes a statement using Westwind.RazorHosting;

This leads to compile errors as class names within that namespace may exist in a different namespace.

For example: class HtmlHelper exists both as in namespace System.Web.Mvc but also in Westwind.RazorHost.

Possible solution: the generated C# code should not use using Westwind.RazorHosting; but instead always use fully qualified names for all of its own classes used in any of the generated sources file.

Calling RenderPartial inside of a Razor helper results in the contents of the partial view being html encoded

Minimal example below.

MainView.cshtml:
@helper RenderSomething()
{
<div>@RenderPartial("PartialView")</div>
}
<html><body>@RenderSomething()</body></html>

PartialView.cshtml:
<table><tr><td>Hello World</td></tr></table>

Expected output:
<html><body><div><table><tr><td>Hello World</td></tr></table></div></body></html>

Actual output:
<html><body><div>&lt;table&gt;&lt;tr&gt;&lt;td&gt;Hello World&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</div></body></html>

The problem appears to be that RazorTemplateBase.WriteTo is not respecting IHtmlString or RawString. I'll submit a pull request with a fix that worked for me.

Behaviour change between Source Code and Nuget Package

I have an interesting issue. When using a Project Reference to the Westwind.RazorHosting source code my application gives me Intellisense in my cshtml files and works perfectly.

If I reference the compiled DLL or the NuGet package, the cshtml file shows an error on

@inherits Westwind.RazorHosting.RazorTemplateFolderHost<DemoModel>

The error is

The type or namespace name 'Westwind' could not be found (are you missing a using directive or an assembly reference?)

Simply switching back the the Project Reference solves the issue an I cannot find any way to make the Nuget package or DLL work.

In both cases, the final application runs fine, I just lose the intellisense.

Using RazorHosting with in an AzureFunction

I'm seeing the following error when using the RazorHosting with in an C# AzureFunction v2 with the nightly build nuget feed

System.Private.CoreLib: Exception while executing function: SendNotification. Microsoft.Azure.WebJobs.Host: Could not load file or assembly 'System.CodeDom, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified

Host Container and Application Wrapper sample code is invalid

There are a few errors in the code snippet under "A better apporach - Host Container and Application Wrapper".

The error out parameter is missing type string.
host.AddAssemblyFromType(this.GetType()); is called from a static method, where this does not exist.
error param should be assigned null when no errors occurred.

Azure Function with this project returns null

Trying to use this project to generate RAZOR HTML in a Azure Function.
I have a private method that gets the RAZOR from embedded resources without any issue. When that, along with it's model, get fed into RenderTemplate(), I get a null result.

Any pointers?

 var host = new Westwind.RazorHosting.RazorEngine();
 html = host.RenderTemplate(GetRazorTemplate(template), emailModel);

private string GetRazorTemplate(EmailTemplates template)
        {
            var assembly = System.Reflection.Assembly.GetExecutingAssembly();

            using (var stream = assembly.GetManifestResourceStream("App.Core.Email." + template + ".cshtml"))
            {
                using (var reader = new System.IO.StreamReader(stream))
                {
                    var result = reader.ReadToEnd();
                    return result;
                }
            }
        }

Skip assemblies already loaded

In some cases when hosting the RazorEngine in a Windows Service we get the following exception at template rendering time:

"An assembly with the same simple name '' has already been imported"

As far as I have understood is due to to the fact that not all the "AddAssembly" methods checks if assemblies are already in the ReferencedAssemblies collection.

Razor engine crashes if @helper{ } is used in the template

... because in RazorEngine.CreateHost the Razor creates default System.Web.Razor.Generator.GeneratedClassContext instance iwith only 3 arguments out of 6.
adding lines
var nonDefault = new System.Web.Razor.Generator.GeneratedClassContext("Execute",
"Write",
"WriteLiteral",
"WriteTo",
"WriteLiteralTo",
"NestedHelper");
host.GeneratedClassContext = nonDefault;

to Core\RazorEngine.cs at line 532 allows to create custom templates that support helpers.

Remoting error due to GC issues when using App Domain

I'm getting the following error:

System.Runtime.Remoting.RemotingException: Object β€˜/76e7cd41_2cd2_4e89_9c03_fae752ec4d59

/zb_uualy_cm6kwizjlentfdl_3.rem’ has been disconnected or does not exist at the server.

It appears to be due to GC issues when using an App Domain. I see where you have a virtual method on the RazorBaseHostContainer called InitializeLifetimeService(). But the Xml Doc seems to indicate that how you have it should keep it alive indefinitely. We are not finding that to be the case.

Any thoughts on this?

Build failure due to missing nuget.exe

Source cannot be built due to the missing nuget.exe file. Build output (path trimmed to "...").

1>------ Build started: Project: Westwind.RazorHosting, Configuration: Debug Any CPU ------
1>C:\...\Westwind.RazorHosting-master\.nuget\NuGet.targets(83,9): error : Unable to locate 'C:\...\Westwind.RazorHosting-master\.nuget\NuGet.exe'
2>------ Build started: Project: Westwind.RazorHosting.Tests, Configuration: Debug Any CPU ------
2>C:\...\Westwind.RazorHosting-master\.nuget\NuGet.targets(83,9): error : Unable to locate 'C:\...\Westwind.RazorHosting-master\.nuget\NuGet.exe'
========== Build: 0 succeeded, 2 failed, 0 up-to-date, 0 skipped ==========

Updated Nuget Package

Hi, I just started using this library and it is great! I just ran into an issue with Layouts, though. I inspected the dlls and it looks like the latest published nuget package is missing the Layout changes. Can you update the nuget package with the latest?

Unable to parse to html

I am getting the following error while parsing to HTML

System.ArgumentOutOfRangeException: Length cannot be less than zero.
Parameter name: length
at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length, Boolean fAlwaysCopy)
at Westwind.RazorHosting.RazorEngine1.FixupTemplate(String template) at Westwind.RazorHosting.RazorEngine1.CompileTemplate(TextReader templateReader, String generatedNamespace, String generatedClassName)
at Westwind.RazorHosting.RazorEngine`1.RenderTemplate(TextReader templateSourceReader, Object model, TextWriter outputWriter)

Is there any workaround for it?

IndexOutOfRangeException throw from GetAssemblyFromStringAndCache()

Hi, I encounter an error that throw from GetAssemblyFromStringAndCache().

I have setup razorhost in this way.

public class RazorTemplateHelper
    {
        public static RazorStringHostContainer RazorHost { get; set; }

        public static string RenderTemplate(string template, object model/*, out string error*/)
        {
            if (RazorHost == null)
                StartRazorHost(); <-- exception throw from here

            //error = "";
            string result = RazorHost.RenderTemplate(template, model);
            if (result == null)
            {
                Debug.WriteLine(RazorHost.ErrorMessage);
                throw new System.Exception(RazorHost.ErrorMessage);
                //error = RazorHost.ErrorMessage;
            }

            return result;
        }

        public static void StartRazorHost()
        {
            var host = new RazorStringHostContainer()
            {
                //// *** Set your Folder Path here - physical or relative ***
                //TemplatePath = Path.GetFullPath(@".\templates\"),
                //// *** Path to the Assembly path of your application
                //BaseBinaryFolder = Environment.CurrentDirectory
            };

            // Add any assemblies that are referenced in your templates
            host.AddAssemblyFromType(typeof(Microsoft.WindowsAzure.Storage.Table.TableEntity));
           <all assembly>
            host.AddAssemblyFromType(typeof(Softinn.Razor.EmailAppSetting));

            // Always must start the host
            host.Start();

            RazorHost = host;
        }

        public static void StopRazorHost()
        {
            RazorHost?.Stop();
        }
    }
Microsoft.Azure.WebJobs.Host.FunctionInvocationException : Exception while executing function: BookingNotification ---> System.IndexOutOfRangeException : Index was outside the bounds of the array.
   at System.Collections.Generic.Dictionary`2.Insert(TKey key,TValue value,Boolean add)
   at Westwind.RazorHosting.RazorStringHostContainer`1.GetAssemblyFromStringAndCache(String templateText)
   at Westwind.RazorHosting.RazorStringHostContainer`1.RenderTemplate(String templateText,Object model,TextWriter writer,Boolean inferModelType)
   at Softinn.Razor.RazorTemplateHelper.RenderTemplate(String template,Object model) at C:\<path to project>\RazorTemplateHelper.cs : 13
...

What I mess up from this configuration? I seem like Razorhost doesn't load assembly correctly and causing this unexpected error.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.