A require module pattern in Asp.Net Razor

Did you ever need to load modules dynamically in Razor? Do you like to write function libraries in pure Razor, and be able to use them from other files without placing that function libraries in App_Code, and without forcing the App to restart?

Not? Oh, well, anyways – as a friday evening experiment I hacked together a Razor Require Module sample which worked out pretty good.

I do recommend the regular way to add functionality, in .cs-files. I use Razor mostly as a pure View engine, with no logic other than view logic.

But Razor can also be a really fun playground to test some ideas in, and with this I can temporarily add functions to for example an Umbraco site directly from within the online Umbraco UI.

Also – the AppPool will restart after a certain number of “recompiles”, which kind of defeats the purpose with all this.

Edit : Roslyn will probably be a better suit for this, see David Ebbo’s post. I wrote an experiment with Roslyn as a server scripting tool.

Here’s the usage:

Create a module, SomeModule.cshtml:

@Require.Define("SomeModule", (module) =>
{
    module.Exclaim = new Func<string, string>((message) => { 
    
        return message + "!"; 
    
    });

    module.AddToDatabase = new Func<dynamic, int>((newrecord)=>{
        
        // some code to add the newrecord to the database        
        // return the created id

        return 0;
    
    });
    
});

The code is using lambda syntax to add functions to the module. The module name is defined with a string.

Later you use the module this way:

@Require.File("~/SomeModule.cshtml")

@{    
    var newId = App.SomeModule.AddToDatabase(new { name = "foo", info = "bar" });    
}

<p>New database item added with ID : @newId</p>

The Require.File function checks if SomeModule has been defined already. If not it will define it and add the code dynamically by loading the SomeModule.cshtml.

Each module is an ExpandoObject, which is added to the global App ExpandoObject. That’s why we use it with “App.ModuleName”.

The Require code needed for this is only a few lines of code in the file Require.Cshtml in App_Code:

@helper File(string fileName) {

    var p = (WebPage)WebPageBase.CreateInstanceFromVirtualPath(fileName);
    var ctx = new WebPageContext(new HttpContextWrapper(HttpContext.Current), p, null);
    p.ExecutePageHierarchy(ctx, new StringWriter());

}
@helper Define(string moduleName, Action<dynamic> definitions, bool alwaysRedefine = false) {
    if (alwaysRedefine || App[moduleName] == null)
    {
        App[moduleName] = new System.Dynamic.ExpandoObject();
        definitions.Invoke(App[moduleName]);
    }
}

If you like to add a function to an already loaded module it’s possible to do so. You need to add a parameter to the define to make it redefine the module even if its already defined:

@Require.Define("OtherModule", (module) =>
{
    @* 
       ...  Existing code ...
    *@

    module.NewFunction = new Func<string>(()=>{
        return "result";
    });
    
}, true);

A module can require other modules. And, you add modules and their functions dynamically without the need for App restarts.

I got the final piece to this puzzle to this code from this gist by Niels Kühnel: dynamically render a razor file http://pastebin.com/fjXPnzAw, thanks Niels.

Using Require in Umbraco
I do most my coding in Visual Studio, nothing beats it. But I also like to be able to log into an online site and make a few additions to a running (small, not client) site, just when I have five minutes over from wherever I am. And with the Umbraco UI I can do that. However, until now I haven been able to re-use code easily.

Tip: Change two settings in web.config to be able to run files from within the api folder directly (“http://mysite.com/macroscripts/api/demo&#8221;):

<appSettings>
...
    <add key="umbracoReservedPaths" value="~/umbraco,~/install/,~/macroscripts/api/" />
...
    <add key="webpages:Enabled" value="true" />
...
Advertisements

One thought on “A require module pattern in Asp.Net Razor

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s