ASP.NET Core
Razor Pages
.NET Core
Web Development
Programming Techniques

Using Razor outside of MVC in .NET Core

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Razor is a templating engine that combines C# with HTML to produce dynamic output. While Razor is most commonly used in ASP.NET MVC views and Razor Pages, it can also be used outside of the web context — for generating HTML emails, PDF templates, code generation, and static site content. The RazorLight library is the most popular choice for standalone Razor rendering, and .NET 6+ also supports RazorEngine through the Razor SDK. This article covers how to render Razor templates in console apps, services, and other non-MVC contexts.

RazorLight is a standalone Razor engine that works without ASP.NET MVC:

bash
dotnet add package RazorLight

Basic Template Rendering

csharp
1using RazorLight;
2
3var engine = new RazorLightEngineBuilder()
4    .UseMemoryCachingProvider()
5    .Build();
6
7string template = @"
8<h1>Hello, @Model.Name!</h1>
9<p>Your order #@Model.OrderId is confirmed.</p>
10<ul>
11    @foreach (var item in Model.Items)
12    {
13        <li>@item</li>
14    }
15</ul>";
16
17var model = new
18{
19    Name = "Alice",
20    OrderId = 12345,
21    Items = new[] { "Widget", "Gadget", "Doohickey" }
22};
23
24string result = await engine.CompileRenderStringAsync("order-template", template, model);
25Console.WriteLine(result);
26// <h1>Hello, Alice!</h1>
27// <p>Your order #12345 is confirmed.</p>
28// <ul><li>Widget</li><li>Gadget</li><li>Doohickey</li></ul>

File-Based Templates

csharp
1var engine = new RazorLightEngineBuilder()
2    .UseFileSystemProject("/path/to/templates")
3    .UseMemoryCachingProvider()
4    .Build();
5
6// templates/OrderConfirmation.cshtml:
7// <h1>Hello, @Model.Name!</h1>
8// <p>Total: @Model.Total.ToString("C")</p>
9
10string html = await engine.CompileRenderAsync("OrderConfirmation.cshtml", model);

Embedded Resource Templates

csharp
1var engine = new RazorLightEngineBuilder()
2    .UseEmbeddedResourcesProject(typeof(Program).Assembly, "MyApp.Templates")
3    .UseMemoryCachingProvider()
4    .Build();
5
6string html = await engine.CompileRenderAsync("Invoice.cshtml", invoiceModel);

HTML Email Generation

The most common use case for Razor outside MVC:

csharp
1// EmailTemplate.cshtml
2// <html>
3// <body>
4//   <h2>Welcome, @Model.UserName!</h2>
5//   <p>Click <a href="@Model.ConfirmUrl">here</a> to confirm your account.</p>
6// </body>
7// </html>
8
9public class EmailService
10{
11    private readonly RazorLightEngine _razor;
12    private readonly IEmailSender _sender;
13
14    public EmailService(RazorLightEngine razor, IEmailSender sender)
15    {
16        _razor = razor;
17        _sender = sender;
18    }
19
20    public async Task SendWelcomeEmail(string email, string userName, string confirmUrl)
21    {
22        var model = new { UserName = userName, ConfirmUrl = confirmUrl };
23        string body = await _razor.CompileRenderAsync("WelcomeEmail.cshtml", model);
24
25        await _sender.SendAsync(email, "Welcome!", body, isHtml: true);
26    }
27}

Using ASP.NET Core's Razor Engine Directly

If you already have ASP.NET Core in your project, you can use IRazorViewEngine without RazorLight:

csharp
1using Microsoft.AspNetCore.Mvc;
2using Microsoft.AspNetCore.Mvc.Rendering;
3using Microsoft.AspNetCore.Mvc.ViewEngines;
4using Microsoft.AspNetCore.Mvc.ViewFeatures;
5
6public class RazorViewRenderer
7{
8    private readonly IRazorViewEngine _viewEngine;
9    private readonly ITempDataProvider _tempDataProvider;
10    private readonly IServiceProvider _serviceProvider;
11
12    public RazorViewRenderer(
13        IRazorViewEngine viewEngine,
14        ITempDataProvider tempDataProvider,
15        IServiceProvider serviceProvider)
16    {
17        _viewEngine = viewEngine;
18        _tempDataProvider = tempDataProvider;
19        _serviceProvider = serviceProvider;
20    }
21
22    public async Task<string> RenderViewToStringAsync<TModel>(
23        string viewName, TModel model, ActionContext actionContext)
24    {
25        var viewResult = _viewEngine.FindView(actionContext, viewName, false);
26        if (!viewResult.Success)
27            throw new InvalidOperationException($"View '{viewName}' not found");
28
29        using var writer = new StringWriter();
30        var viewData = new ViewDataDictionary<TModel>(
31            new EmptyModelMetadataProvider(), new ModelStateDictionary())
32        {
33            Model = model
34        };
35        var tempData = new TempDataDictionary(
36            actionContext.HttpContext, _tempDataProvider);
37
38        var viewContext = new ViewContext(
39            actionContext, viewResult.View, viewData, tempData,
40            writer, new HtmlHelperOptions());
41
42        await viewResult.View.RenderAsync(viewContext);
43        return writer.ToString();
44    }
45}

Console Application Example

csharp
1// Program.cs — .NET 6+ console app
2using RazorLight;
3
4var engine = new RazorLightEngineBuilder()
5    .UseFileSystemProject(Path.Combine(Directory.GetCurrentDirectory(), "Templates"))
6    .UseMemoryCachingProvider()
7    .Build();
8
9// Generate an HTML report
10var reportData = new
11{
12    Title = "Monthly Sales Report",
13    GeneratedAt = DateTime.Now,
14    Sales = new[]
15    {
16        new { Product = "Widget A", Revenue = 15000m, Units = 300 },
17        new { Product = "Widget B", Revenue = 22000m, Units = 450 },
18    }
19};
20
21string html = await engine.CompileRenderAsync("Report.cshtml", reportData);
22File.WriteAllText("report.html", html);
23Console.WriteLine("Report generated: report.html");

Dependency Injection Setup

csharp
1// In a non-MVC project with DI
2services.AddSingleton<RazorLightEngine>(sp =>
3{
4    return new RazorLightEngineBuilder()
5        .UseFileSystemProject(Path.Combine(Directory.GetCurrentDirectory(), "Templates"))
6        .UseMemoryCachingProvider()
7        .Build();
8});
9
10services.AddTransient<EmailService>();

Common Pitfalls

  • Template compilation is slow on first run: Razor templates are compiled to C# then to IL on first use. Subsequent renders use the cached compiled template. Pre-compile templates at startup for latency-sensitive applications.
  • Thread safety with RazorLight: RazorLightEngine is thread-safe and should be registered as a singleton. Creating a new engine per request wastes compilation cache and memory.
  • Model type issues with anonymous objects: Anonymous types are internal in C#. RazorLight may not be able to access properties across assembly boundaries. Use dynamic or define a concrete model class when templates are in a separate assembly.
  • Missing Razor SDK in console projects: Console apps do not include the Razor SDK by default. Use RazorLight (which bundles its own compilation) rather than trying to manually configure the ASP.NET Razor SDK in a non-web project.
  • HTML encoding by default: Razor automatically HTML-encodes output with @Model.Value. To output raw HTML, use @Html.Raw(Model.HtmlContent). Forgetting this causes HTML tags to appear as escaped text in emails.

Summary

  • Use RazorLight for standalone Razor rendering in console apps, services, and background jobs
  • Templates can be loaded from strings, files, or embedded resources
  • HTML email generation is the most common non-MVC use case for Razor
  • Register RazorLightEngine as a singleton for template compilation caching
  • Use @Html.Raw() for raw HTML output — Razor HTML-encodes by default

Course illustration
Course illustration

All Rights Reserved.