Thursday, 3 October 2013

Executing Razor Views outside of a web application with NonHttpRunTimeRazorSupport

NonHttpRunTimeRazorSupport (catchy) is a small library that allows you to use ASP.Net MVC Razor views to render content outside of the context of a running ASP.Net application  with full support for Layouts, _ViewStart inclusion, Partials, built-in HtmlHelpers etc.


Introduction

Razor views are a really nice mechanism for generating web page content within an ASP.Net MVC application. They're so great in fact, that it's a shame to use them just to render HTML to display in a browser. What if we could use them to generate other content within our application, like email messages?

Many applications need to send emails. For something simple, like a one-off message to a site administrator, you might be able to get away with using a StringBuilder to generate the content of the email. If you need to send a range of rich HTML emails and want developers to be able to modify them easily, a full-blown template system like Razor is a much more flexible and maintainable mechanism.

Another common requirement is to send emails in an "offline" scenario. By "offline", I mean outside of the context of an HTTP request in a running ASP.Net application. Maybe you want to send emails within a console app, Windows service or an NServiceBus / MassTransit / Rebus message handler.

NonHttpRunTimeRazorSupport lets you use all of Razor's features outside of a running web app.

There are loads of other things you could use this library for - but email is probably one of the most common requirements it supports.

How it Works

The library is on GitHub at https://github.com/danmalcolm/NonHttpRunTimeRazorSupport. If you know about git, go ahead and clone the repo on your development machine, otherwise you can download a zip file of the source or browse the code directly on GitHub.

This solution uses RazorGenerator, a great tool that allows you to pre-compile Razor views at design time. As you edit your Razor views in Visual Studio, a pre-compiled view (an accompanying C# class file a bit like a web forms Something.aspx.Designer.cs file). Pre-compiled views can be used instead of .cshtml files that are compiled on-the-fly by RazorViewEngine. One thing that the RazorGenerator project is missing is an example of how to render pre-compiled Razor views in the "offline" context we introduced above*.

Razor View templates, whether compiled at design-time by RazorGenerator or at runtime by RazorViewEngine, are instances of WebViewPage. Views of this type are designed to execute in the context of an HTTP request. The standard built-in RazorViewEngine assumes a web request and other supporting infrastructure offered by HttpRuntime to be present when locating and rendering Razor views. 

The main purpose of NonHttpRunTimeRazorSupport is to work around this limitation. A customised ViewEngine and fake implementations of HttpContext (and it's HttpRequest, HttpResponse members) are used to support the execution of Razor Views.

Needless to say, you have to write your templates with this "offline" context in mind. Putting something like @HttpContext.Current.Request.ServerVariables[...] in the middle of your template would result in an exception. Note that the library attempts to provide help here by throwing an exception with an informative message if  any non-supported HttpContext (and family) members are accessed.

Getting Started

See the README for details on how to get up and running.

The repository includes a simple EmailDemo console app, that demonstrates how you might handle sending emails in your application.

The EmailNotificationService class uses Razor templates to render email content, using a convention to locate views based on the name of the model type. Here's how the views are structured:


This foundation would allow you to support a range of emails without adding a great deal of code. For each message type, you would simply need to add a model and Razor templates to render the subject, text and html body content. Here's the code for EmailNotificationService in full:

Look at EmailNotificationService.cs on github if you can't see the code in-line above.

Alternatives

There are other libraries that allow you to use Razor templates to generate email content, such as MvcMailer and ActionMailer, but, they don't fully support sending Razor-templated emails in an "offline" scenario ("Email sending from a background process" is still listed as an upcoming feature on the MvcMailer Wiki page and the standalone version of ActionMailer doesn't appear to support layouts and other Razor features.

What Next?

I'll consider adding a NuGet package once I've got feedback from using this approach on some other projects. Any comments or feedback would be great.

I came up with this solution while working on a project that was already using RazorGenerator. I'm interested in exploring whether it would be possible to bypass RazorGenerator and compile the views on the fly. Compiling Razor views from templates stored in a data store could also be interesting....

Happy mailing!

* I should point out that views compiled with the RazorGenerator "Template" generator can run outside of the context of an ASP.Net application. They are not the same thing as standard MVC Razor views - the declarative Layout property, _ViewStart pages, partials, HtmlHelper / UrlHelper etc are not available.


24 comments:

  1. Email is one of those things that could easily be offloaded to a background service. Sadly, MvcMailer gives me access to the HttpContext which allows me to generate urls respective to my environment. Not a huge deal, but can be very helpful when you have a dev, staging, and production environment. I guess the easy solution to that is just creating app settings with the proper base url.

    ReplyDelete
    Replies
    1. This solution renders templates with a special implementation of HttpContext to support "offline" use, with the properties needed to support UrlHelper etc. It's pretty nice to be able to use the following in an email template rendered by a console app:

      <a href="@Url.Absolute("~/tnc")">terms and conditions</a>

      You need to supply the base URL when you render the template, e.g.:

      string content = renderer.RenderView(model, baseUri);

      I see what you're saying - rendering an email directly within a web app means that it automatically uses the right host name in URLs. Usually, a background service will have a separate deployment per environment, e.g. test and production, so the config file solution isn't too difficult.

      If you're queueing email from more than one site to be sent by a shared background service, you could include the base URL in the command / message ... whatever you're using.

      In one project scenario (multi-tenant e-commerce), a background service gets the URL for each storefront site from the database.

      Delete
  2. Interesting how does this compare with Matthew Abbott's RazorEngine that is already a Nuget package ? I've tried the RazorEngine, but I'm struggling a bit to do anything more than the basics due to the lack of documentation. I will try this tool as well and see how far I can get. Thanks for your efforts !

    ReplyDelete
  3. They may do not have the fundamental aptitudes expected to build up a completely custom web webpage. bahrain web development

    ReplyDelete
  4. A wide range of Search Engine Optimization and Digital Marketing techniques design and develop by the top business analysis in Malaysia.
    serpmind malaysia

    ReplyDelete

  5. This is genuinely an awesome read for me. I have bookmarked it and I am anticipating perusing new articles. Keep doing awesome!
    Web Development

    ReplyDelete
  6. Domain hosting wiki: for bloggers, business owners and webmasters looking for starting, maintaining and knowing more about domains and hosting
    Domain

    ReplyDelete
  7. we need to set a relationship that produces an adequate measure of put stock in, certainty, dependability and energy. https://edkentmedia.com/toronto-website-design-development/

    ReplyDelete
  8. Recently checked your web development services. Your five star ratings have really impressed me. web development services

    ReplyDelete
  9. The primary things to remember while picking an innovative web design company are their design to usage process, their cost, their portfolio, and at last the level of administration you can anticipate.San Diego Web Design

    ReplyDelete
  10. That can spare you a ton of time. How might you want to invest weeks building up a website, just to find that your customer needs totally extraordinary text styles, hues, illustrations, webpage association and substance? SEO 2018

    ReplyDelete
  11. Hello, I have browsed most of your posts. This post is probably where I got the most useful information for my research. Thanks for posting, maybe we can see more on this. Are you aware of any other websites on this subject.
    https://www.razorwang.com/single-blade-disposable-shaving-razors-manufacturer

    ReplyDelete
  12. This is such a great resource that you are providing and you give it away for free. I love seeing blog that understand the value of providing a quality resource for free.
    software development company in delhi

    ReplyDelete
  13. Webbies often get asked to produce a website with the "WOW factor".  Webceed

    ReplyDelete
  14. I'm constantly searching on the internet for posts that will help me. Too much is clearly to learn about this. I believe you created good quality items in Functions also. Keep working, congrats! top web designers

    ReplyDelete
  15. This particular papers fabulous, and My spouse and i enjoy each of the perform that you have placed into this. I’m sure that you will be making a really useful place. I has been additionally pleased. Good perform! buy youtube likes

    ReplyDelete
  16. wow what an incredible post is this... a debt of gratitude is in order for sharing manager, keep share these profitable substance.
    local internet marketing company

    ReplyDelete
  17. Our services are available in all cities of Minneapolis and its surroundings. We are having years of experience which enabled our customers and clients to trust our services. lesmeilleursvpn

    ReplyDelete
  18. Took me time to read all the comments, but I really enjoyed the article. It proved to be Very helpful to me and I am sure to all the commenters here! It’s always nice when you can not only be informed, but also entertained! COR Designer

    ReplyDelete
  19. I think this is definitely an amazing project here. So much good will be coming from this project. The ideas and the work behind this will pay off so much. privacidadenlared

    ReplyDelete
  20. It was wondering if I could use this write-up on my other website, I will link it back to your website though.Great Thanks. hack mobile strike

    ReplyDelete
  21. Awesome things you've generally imparted to us. Simply continue written work this sort of posts.The time which was squandered in going for educational cost now it can be utilized for studies.Thanks vpnforexpats

    ReplyDelete
  22. I utilized it for a month and saw a discernible distinction in myself. Presently I don't short of going out with my companions and so forth. lemigliorivpn.com

    ReplyDelete