ASP.Net Web API and using Razor the next step

In my previous blog post “Using Razor together with ASP.NET Web API” I wrote about a solution to use a MediaTypeFormatter to render HTML by using Razor when the API is accessed from a browser. I’m now sort of done with the basics and will share the current solution in this blog post. The source code will later be available.

I decided to make the solution more extendable so I created a HtmlMediaTypeViewFormatter that inherits from the MediaTypeFormatter:

<span><br>public</span> <span>class</span> HtmlMediaTypeViewFormatter : MediaTypeFormatter
{
    <span>//...</span>
}

 

With the new generic solution any kind of “parser” can be used to render the HTML, not only by using Razor. I made this possible by using a IViewParser:

<span><br>public</span> <span>interface</span> IViewParser
{
   <span>byte</span>[] ParseView(IView view, <span>string</span> viewTemplate, Encoding encoding);
}


The IViewParser’s responsibility is to implement the logic to use a template (for example a razor template) and parse a view’s model into a encoded byte array.

Another interface that is introduce is the IViewLocator, with the IViewLocator it will be easy to replace how to locate the templates passed into the IViewParser. At the moment the IViewLocator also has the responsibility to return the content of the located template, so the default RazorViewLocator in the project will locate and read a .cshtml or .vbhtml file and the content of the file is passed as an argument to the IViewParser. Here is part of the IViewLocator code:

<span>public</span> <span>interface</span> IViewLocator
{
    <span>string</span> GetView(<span>string</span> siteRootPath, IView view);
}

<span>internal</span> <span>class</span> RazorViewLocator : IViewLocator
{
     <span>private</span> <span>readonly</span> <span>string</span>[] viewLocationFormats = <span>new</span>[]
        {
             <span>"~\\Views\\{0}.cshtml"</span>,
             <span>"~\\Views\\{0}.vbhtml"</span>,
             <span>"~\\Views\\Shared\\{0}.cshtml"</span>,
             <span>"~\\Views\\Shared\\{0}.vbhtml"</span>
         };

     <span>public</span> <span>string</span> GetView(<span>string</span> siteRootPath, IView view)
     {
         <span>if</span> (view == <span>null</span>)
             <span>throw</span> <span>new</span> ArgumentNullException(<span>"view"</span>);

         var path = GetPhysicalSiteRootPath(siteRootPath);

         <span>foreach</span>(<span>string</span> viewLocationFormat <span>in</span> viewLocationFormats)
         {
             var potentialViewPathFormat = viewLocationFormat.Replace(<span>"~"</span>, GetPhysicalSiteRootPath(siteRootPath));

             var viewPath = <span>string</span>.Format(potentialViewPathFormat, view.ViewName);

             <span>if</span> (File.Exists(viewPath))
                 <span>return</span> File.ReadAllText(viewPath);
         }

         <span>throw</span> <span>new</span> FileNotFoundException(<span>string</span>.Format(<span>"Can't find a view with the name '{0}.cshtml' or '{0}.vbhtml in the '\\Views' folder under  path '{1}'"</span>, view.ViewName, path));
     }

      <span>///...</span>
}


Here is some code from the HtmlMediaTypeViewFormatter, so you can get a glimpse how the IViewLocator and IViewParser is used:

<span>public</span> <span>override</span> Task WriteToStreamAsync(
                                       Type type,
                                       <span>object</span> <span>value</span>,
                                       Stream stream,
                                       HttpContentHeaders contentHeaders,
                                       TransportContext transportContext)
{
     <span>return</span> TaskHelpers.RunSync(() =&gt;
           {
              var encoding = SelectCharacterEncoding(contentHeaders);

              var parsedView = ParseView(type, <span>value</span>, encoding);

              stream.Write(parsedView, 0, parsedView.Length);
              stream.Flush();
           });
}

<span>private</span> <span>byte</span>[] ParseView(Type type, <span>object</span> model, System.Text.Encoding encoding)
{
      var view = model <span>as</span> IView;

       <span>if</span> (view == <span>null</span>)
            view = <span>new</span> View(GetViewName(model), model, type);

        var viewTemplate = _viewLocator.GetView(_siteRootPath, view);

        <span>return</span> _viewParser.ParseView(view, viewTemplate, encoding);
}


Note: The WriteToStreamAsync in the RTM code of ASP.NET WebAPI is different from the above code. I decided to still use the bits shipped with Visual Studio 2012 RC. This will of course be changed later.

The IViewLocator and IViewParser can be changed by using a global configuration class, GlobalViews, so in App_Start of a Web API project or in Global.asax the locator and parser can be replaced with another implementation, for example:

GlobalViews.DefaultViewLocator = <span>new</span> MyViewParser();
GlobalViews.DefaultViewLocator = <span>new</span> DatabaseViewLocator();

 

The HtmlMediaTypeViewFormatter can also inject the dependencies to a locator and parser.

How to decide which view to be used


There are different ways to configure which View that should be used when accessing a API of a ApiController. Here are the examples:

Convention

<span>public</span> <span>class</span> CustomerController : ApiController
{
     <span>// GET api/customer</span>
     <span>public</span> Customer Get()
     {
        <span>return</span> <span>new</span> Customer { Name = <span>"John Doe"</span>, Country = <span>"Sweden"</span> };
     }
}

 

By default with no configurations at all (just adding the HtmlMediaTypeViewFormatter to the Formatters collection in the Global.asax) a view will be located by using the name of the returned model, in the above code “Customer”

 

Configuration

By using annotation (with the ViewAttribute) you can specify which view a specific model should use:

 

[View(<span>"Customer"</span>)]
<span>public</span> <span>class</span> Customer
{
    <span>public</span> <span>string</span> Name { get; set; }

    <span>public</span> <span>string</span> Country { get; set; }
}

 

By using the GlobalViews in the Global.asax, mapping between model an views can be configured, here is code from the App_Start folder:

<span>public</span> <span>class</span> ViewConfig
{
    <span>public</span> <span>static</span> <span>void</span> RegisterViews(IDictionary&lt;Type, <span>string</span>&gt; views)
    {
        views.Add(<span>typeof</span>(Customer), <span>"CustomerViaConfig"</span>);
    }
}


So if the Customer type is returned from the Web API, the “CustomerViaConfig” view will be used.

By returning an IView class, the view can be specified within the Web API’s return result:

<span>public</span> <span>class</span> CustomerController : ApiController
{
   <span>// GET api/customer</span>
   <span>public</span> View Get()
   {
      <span>return</span> <span>new</span> View(<span>"CustomerViaView"</span>, <span>new</span> Customer { Name = <span>"John Doe"</span>, Country = <span>"Sweden"</span> });
   }
}

 

Summary

By using a HtmlMediaTypeViewFormatter, we can now render HTML when accessing our Web API from a browser, and we can specify which view to be used by using different kind of configuration or convention. It’s easy to replace the parser that is used to render the HTML and also how and where to locate a view to be used. The source code will sometime in a near future (I hope) be available for download.

My next goal is to make a WHOLE web site explaining the use of my project, just by using ASP.Net Web API.. I promise to let all of you know when that happens.

 

If you want to know when I post a new blog post, you can simply follow me on twitter @fredrikn

Read More

Programmeringsglädje del 2: Scala

För några dagar sedan bloggade jag om Programmeringsglädje. Idag var det dags att fortsätta min högst spontana programmeringsresa genom att skriva om patienslösarprogrammet i Scala. Scala har de senaste åren blivit allt mer uppmärksammat även utanför entusiastkretsarna. Redan 2010 var IDG igång och propagerade för Scala som ett alternativ till Java. Själv har jag sneglat […]

Read More

Programmeringsglädje!

Så här i semestertider kanske man borde koppla bort allt vad datorer, mjukvara och projekt heter och ägna sig åt annat. Men regniga dagar kommer ändå skaparlusten fram i mig. Då jag inte har särskilt mycket Ernst i mig vänder jag mig istället till den digitala domänen. Härom kvällen lade svägerskan några olika patienser och […]

Read More