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

Using Razor outside of MVC in .NET Core

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

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
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms