mcpbeat

Migrating Razorengine To Razorlight

microsoft/migrating-razorengine-to-razorlight

> Migrates the deprecated RazorEngine to RazorLight for Razor template rendering outside of MVC. Use ONLY when RazorEngine has been flagged as deprecated or unmaintained and must be replaced — not for version-bump scenarios where RazorEngine is still supported.

1k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
17
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill migrating-razorengine-to-razorlight

The instruction itself

18 sections, as written by the author

RazorEngine to RazorLight Migration

Overview

Migrate Razor-based template rendering from RazorEngine to RazorLight. RazorEngine relies on a global static Engine.Razor singleton and synchronous compilation, which is incompatible with modern .NET. RazorLight provides an async-first API, built-in dependency injection support, and runs on .NET 6+. The core change is replacing Engine.Razor.RunCompile() with RazorLightEngine.CompileRenderAsync().

Package Reference Changes

Old References (Remove)

<PackageReference Include="RazorEngine" Version="*" />
<!-- Also remove if present -->
<PackageReference Include="RazorEngine.NetCore" Version="*" />

New Reference (Add)

<PackageReference Include="RazorLight" Version="{latest-stable}" />

Workflow

Migration Progress:
- [ ] Step 1: Detect RazorEngine usage
- [ ] Step 2: Update package references
- [ ] Step 3: Replace engine initialization
- [ ] Step 4: Migrate template rendering calls
- [ ] Step 5: Migrate custom template sources
- [ ] Step 6: Update caching logic
- [ ] Step 7: Build and verify

Step 1: Detect RazorEngine Usage

Scan the project for:

  • using RazorEngine; and using RazorEngine.Templating; statements
  • Calls to Engine.Razor.RunCompile(), Engine.Razor.Compile(), Engine.Razor.Run()
  • Custom ITemplateSource or ITemplateResolver implementations
  • TemplateServiceConfiguration setup code

Step 2: Update Package References

Remove RazorEngine (or RazorEngine.NetCore) and add RazorLight in the project file.

Step 3: Replace Engine Initialization

| Old (RazorEngine) | New (RazorLight) |

|-------------------|------------------|

| Engine.Razor (global static, no setup) | new RazorLightEngineBuilder().UseMemoryCachingProvider().Build() |

| var config = new TemplateServiceConfiguration(); var service = RazorEngineService.Create(config); | new RazorLightEngineBuilder().UseEmbeddedResourcesProject(typeof(MyClass)).Build() |

Register the engine in DI as a singleton because RazorLightEngine is thread-safe and caches compiled templates:

services.AddSingleton<IRazorLightEngine>(sp =>
    new RazorLightEngineBuilder()
        .UseMemoryCachingProvider()
        .Build());

Step 4: Migrate Template Rendering Calls

All RazorLight rendering methods are async. Update call sites to use await.

| Old (RazorEngine) | New (RazorLight) |

|-------------------|------------------|

| Engine.Razor.RunCompile(templateSource, "key", typeof(T), model) | await engine.CompileRenderStringAsync("key", templateSource, model) |

| Engine.Razor.Compile(templateSource, "key") then Engine.Razor.Run("key", typeof(T), model) | await engine.CompileRenderStringAsync("key", templateSource, model) |

| Engine.Razor.IsTemplateCached("key", typeof(T)) | Check via engine.Options.CachingProvider.RetrieveTemplate("key") |

The typeof(T) model-type parameter is not needed in RazorLight — the engine infers the model type automatically.

Step 5: Migrate Custom Template Sources

If the project uses custom template resolution (loading templates from a database, embedded resources, or custom paths), implement RazorLight's RazorLightProject:

| Old (RazorEngine) | New (RazorLight) |

|-------------------|------------------|

| ITemplateResolver | Inherit from RazorLightProject |

| ITemplateSource | Return RazorLightProjectItem from your project |

Configure the engine to use the custom project:

var engine = new RazorLightEngineBuilder()
    .UseProject(new MyCustomProject())
    .UseMemoryCachingProvider()
    .Build();

For file-based templates, use the built-in file project:

var engine = new RazorLightEngineBuilder()
    .UseFileSystemProject("/path/to/templates")
    .Build();

Step 6: Update Caching Logic

RazorLight handles caching internally via ICachingProvider. Remove any manual template caching code that was needed for RazorEngine. The MemoryCachingProvider (default) caches compiled templates in memory automatically.

Step 7: Build and Verify

  • Build the project:
   dotnet build
  • Test template rendering with representative templates
  • Verify dynamic model properties resolve correctly
  • Confirm async call chains propagate correctly through the application

Troubleshooting

Template Compilation Errors at Runtime

RazorLight compiles templates at runtime using the Razor SDK. Ensure the project targets a framework that includes the Razor runtime components. If targeting a worker service or console app, add <PreserveCompilationContext>true</PreserveCompilationContext> to the project file.

Missing Model Properties

RazorLight uses dynamic models differently than RazorEngine. If using anonymous types, pass them as ExpandoObject instances or use strongly-typed models.

Thread Safety

RazorLightEngine is thread-safe and should be registered as a singleton. Creating a new engine per request causes excessive compilation overhead and memory pressure.

Cached Template Not Updating

During development, the memory caching provider does not detect source changes. Clear the cache or restart the application to pick up template edits.

How to use it

Copy the folder

Take microsoft/migrating-razorengine-to-razorlight from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.