In previous versions of MVC framework, running on top of the “classic” ASP.NET runtime, it was quite common for developers to switch view compilation on, so that the views get compiled upfront, allowing you to see any errors at compile time, rather than at runtime.
This was done by a simply adding
Given that everything changes in the new ASP.NET 5 world, how would you do it now? Let’s explore.
Introducing ICompileModule π
KRuntime contains an assembly neutral ICompileModule which can be used for various meta-programming purposes. It allows you to process the Roslyn’s CSharpCompilation object prior to the compilation being emitted (but after the compilation object was created), as well as access to the assembly and PDB/MDB symbol afterwards. Additionally, it gives you insight to the Roslyn emitted diagnostics.
You can use ICompileModule for a bunch of purposes, mainly to customize the way the compilation should happen.
namespace Microsoft.Framework.Runtime
{
[AssemblyNeutral]
public interface ICompileModule
{
void BeforeCompile(IBeforeCompileContext context);
void AfterCompile(IAfterCompileContext context);
}
[AssemblyNeutral]
public interface IAfterCompileContext
{
CSharpCompilation CSharpCompilation { get; set; }
IList<Diagnostic> Diagnostics { get; }
}
[AssemblyNeutral]
public interface IBeforeCompileContext
{
CSharpCompilation CSharpCompilation { get; set; }
IList<ResourceDescription> Resources { get; }
IList<Diagnostic> Diagnostics { get; }
}
}
How do you register modules then? Well you don’t. The Roslyn compiler, or rather the ASP.NET 5 worker class that wraps the Roslyn compiler and is responsible for creating the compilation (also processing Assembly Neutral Interfaces etc), will simply find all ICompileModule that exist within the source files that are subject to compilation, and apply them.
RazorPreCompileModule π
Using a custom ICompileModule, ASP.NET MVC 6 can provide the capability of building views. You don’t even have to implement it on your own, because it’s already there - it’s called RazorPreCompileModule.
The class is abstract so you will just have to inherit from it, and put it somehwere in the code. Plus, as already mentioned, in order fot ICompileModule to kick in, you have to have it declared somehwere in your code anyway.
For example:
public class EnforceRazorViewCompilation : RazorPreCompileModule
{
public EnforceRazorViewCompilation(IServiceProvider provider) : base(provider)
{
}
}
RazorPreCompileModule will internally use RazorPreCompiler class, which will use - surprise - Roslyn, to generate syntax trees for your views and include them in the Roslyn’s compilation object, which will later be emitted, as well as throw into the context object any diagnostics it produces, so that those can be picked up by your design time host.
Actually, all methods on RazorPreCompiler, as well as on RazorPreCompileModule are virtual so it’s very easy to extend the logic or throw in your own custom syntax trees should you ever need to do so (perhaps expand your own custom placeholders into code etc).
Trying it out π
Note: This functionality does not work correctly in 1.0.0-beta1, which is the version used by Visual Studio 2015 preview! This means, you will have to use nightly builds to try it out right now, or wait for the next milestone release. Here’s a good wiki article explaining how to get started with nightlies.
Let’s consider the following simple example:
A model with a controller:
namespace Foo
{
public class AccountController : Controller
{
public IActionResult Index()
{
return View(new UserModel { Name = "Filip", AccountStatus = Status.Active });
}
}
public class UserModel
{
public string Name { get; set; }
public Status AccountStatus { get; set; }
}
public enum Status {
Active, Inactive
}
}
And a view:
@using Foo;
@model UserModel
# Hello, @Model.Name
Your account status is @(Model.AccountStatus ? "active" : "inactive")
You can immediately see that this should fail, as we are asssuming Model.AccountStatus is bool, whereas it’s actually an enum. However, since it’s in Razor, this code will compile and you would only see the problem at runtime.
However, the moment you put in the the custom EnforceRazorViewCompilation we just defined moments ago in your code, you will see the build fail as expected:
In this case I’m seeing the error, as it’s from the Desktop CLR and Core CLR.