:::: MENU ::::

Monday, November 23, 2015

Today I was working on a blog plug-in for an existing application. The application is an ASP.NET MVC application so the application uses MVC’s routing to handle file access for the most part. One of the requirements for the blogging component is that it has to integrate with Windows Live Writer handling posting and updates to posts. One of the requirements for Live Writer to supported extended post properties when uploading new posts is a Live Writer Manifest file.

Serving the wlwmanifest.xml File from a Subfolder

The issue is that the manifest file has to live at a very specific location in the blog's root folder using an explicit filename.
If your site is running from a Web root, that's not a problem – it's easy to link to static files in the Web root, because MVC is not managing the root folder for routes (other than the default empty ("") route). So if you reference wlwmanifest.xml in the root of an MVC application by default that just works as IIS can serve the file directly as a static file.
Problems arise however if you need to serve a 'virtual file' from a path that MVC is managing with a route. In my case the blog is running in a subfolder that is a MVC managed routed path – /blog. Live writer now expects the wlwmanifest.xml file to exist in the /blog folder, which amounts to a URL like the following:
Sounds simple enough, but it turns out mapping this very specific and explicit file path in an MVC application can be tricky.

MVC works with Extensionless URLs only (by default)

ASP.NET MVC automatically handles routing to extensionless urls via the IIS ExtensionlessRouteHandler which is defined in applicationhost.config:
<system.webServer> <handlers> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" /> </handlers> </system.webServer>
Note the path="*." which effectively routes any extensionless URLs to the TransferRequestHandler which is MVC's entrypoint.
This handler routes any extensionless URLs to the MVC Routing engine which then picks up and uses the routing framework to route requests to your controller methods – either via default routes of controller/action or via custom routes defined with [Route()] attributes. This works great for MVC style extensionless routes that are typically used in MVC applications.

Static File Locations in Routed directories

However, things get tricky when you need to access static files in a directory that MVC routes to. For the Live Write scenario particularly I need to route to:
The problem is:
  • There’s no physical blog folder (wlwmanifest.xml resides in the root folder)
  • /blog/ is routed to by MVC
  • /blog/ is a valid and desirable MVC route
  • wlwmanifest.xml can’t be physically placed in this location
And that makes it rather difficult to handle the specific URL Live Writer expects in order to fine the manifest file.
There are a couple of workarounds.

Skip Routing use UrlRewrite

After futzing around with a bunch of different solutions inside of MVC and the routing setup, I instead decided to use the IIS UrlRewrite module to handle this. In retrospect this is the most efficient solution since IIS handles this routing at a very low level.
To make this work make sure you have the IIS Rewrite Module installed – it’s an optional component and has to be installed via the IIS Platform installer.
Then add the following to your web.config file:
<system.webServer>
  <rewrite>
    <rules>
      <rule name="Live Writer Manifest">
        <match url="wlwmanifest.xml"/>
        <action type="Rewrite" url="blog/manifest"/>
      </rule>
    </rules>
  </rewrite>
</system.webServer>
This effectively routes any request to wlwmanifest.xml on any path to a custom MVC Controller Method I have set up for this. Here’s what the controller method looks like:
[AllowAnonymous]        
[Route("blog/manifest")]
public ActionResult LiveWriterManifest()
{            
    return File(Server.MapPath("~/wlwmanifest.xml"), "text/xml");
}
This is an efficient and clean solution that is fixed essentially through configuration settings. You simply redirect the physical file URL into an extensionless URL that ASP.NET can route as usual and that code then simply returns the file as part of the Response. The only downside to this solution is that it explicitly relies on IIS and on an optionally installed component.

Custom Path to TransferRequestHandler

Another, perhaps slightly safer solution is to map your file(s) to the TransferRequestHandler Http handler, that is used to route requests into MVC. I already showed you that the default path for this handler is path="*." but you can add another handler instance into your web.config for the specific wildcard path your want to handle. Perhaps you want to handle all .xml files (path="*.xml") or in my case only a single file (path="wlwmanifest.xml").
Here's what the configuration looks like to make the single wlwmanifest.xml file work:
<system.webServer>
  <handlers>
    <add name="Windows Live Writer Xml File Handler"
      path="wlwmanifest.xml"
      verb="GET" type="System.Web.Handlers.TransferRequestHandler"
      preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0"  />
  </handlers>
</system.webServer>
Once you do this, you can now route to this file by using an Attribute Route:
[Route("blog/wlwmanifest.xml")]
public ActionResult LiveWriterManifest()
{            
    return File(Server.MapPath("~/wlwmanifest.xml"), "text/xml");
}
or by configuring an explicit route in your route config.

Enable runAllManagedModulesForAllRequests

If you really want to route files with extensions using only MVC you can do that by forcing IIS to pass non-Extensionless Urls into your MVC application. You can do this by enabling the  runAllManagedModulesForAllRequests option on the section in the IIS configuration for your site/virtual:
<system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
</system.webServer> 
While this works to hit the custom route handler, it’s not really something I typically want to enable as it routes every type of document – including static files like images, css, javascript – through the MVC pipeline which adds overhead. Unless you’re already doing this to perform special manipulation of static files, I wouldn’t recommend enabling this option.

Other Attempts

As is often the case, all this looks straight forward in a blog post like this but it took a while to actually track down what was happening and realizing that IIS was short-circuiting the request processing for the .xml file.
Before I realized this though I went down the path of creating a custom Route handler in an attempt to capture the XML file:
public class CustomRoutesHandler : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var url = httpContext.Request.Url.ToString();

        if (url.ToLower().Contains("wlwmanifest.xml"))
        {
            httpContext.Response.ContentType = "text/xml";
            httpContext.Response.TransmitFile("~/wlwmanifest.xml");
            httpContext.Response.End();
        }        
        return null;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext,
                RouteValueDictionary values)
    {
        return null;
    }
}
To hook up a custom route handler:
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");            
            
    routes.Add(new CustomRoutesHandler());

    routes.MapMvcAttributeRoutes();

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}
The route handler explicitly checks each request coming in and then overrides the behavior to load the static file.
But alas,  this also doesn’t work by default because just like a route that tries to look at an XML file, the file is never actually passed to the MVC app because IIS handles it.
Nevertheless it's good to know t that MVC has the ability to allow you to look at every request it does handle and customize the route or processing along the way which allows for short circuiting of requests which can be useful for special use cases. Irrelevant to my initial problem, but useful and worthwhile to mention in this context :-)

Summary

File based URL access is one of those cases that should be super simple and obvious, but is not. It requires a relatively simple but non-obvious workaround to ensure that you can handle a Url with an extension by either using UrlRewrite or adding an explicit file mapping to the TransferRequestHandler.
Incidentally in ASP.NET 5 (MVC 6) – assumes you’re handling all requests anyway as you are expected to build up the entire request pipeline – including static file handling from scratch. So I suspect in future versions of MVC this sort of thing will be more natural, as long as the host Web server stays out of the way…

Monday, September 7, 2015

A common beginner question is - How to handle multiple submit buttons in ASP.NET MVC?
In web forms this is quite straightforward because all you need to do is write the Click event handler of the respective buttons. However, in ASP.NET MVC you need to rely on the plain HTML and / or  JavaScript to deal with the situation. To that end this article illustrates four ways of handling multiple submit buttons.
The following code uses a simple Customer model class that looks like this:

public class Customer
{
    public string CustomerID { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string Country { get; set; }
}
 
The Index view accepts values for all the properties of the Customer classly CustomerID, CompanyName, ContactName and Country.

1. Multiple buttons with different names

In this technique you use BeginForm() helper as usual and submit a form to an action method. The relevant markup from the Index view that does this is shown below:

@model MultipleSubmitButtons.Models.Customer
...
...
@using (Html.BeginForm("ProcessForm", "Home", FormMethod.Post))
{
    @Html.EditorForModel()
    

    < input type="submit" name="save" value="Save" />
    < input type="submit" name="cancel" value="Cancel" />
}
 
As you can see the BeginForm() helper specifies the action name to be ProcessForm, controller name to be Home and the form method to be POST. To display a data entry textboxes EditorForModel() helper is used. You can very well use helpers such as TextBoxFor() and LabelFor() if you so wish.
There are two submit buttons - one with name attribute set to save and the other with name of cancel. When you click a particular button, its name and value is sent to the server. The ProcessForm() action needs to grab these values to detect which one was clicked.
This is how the Index view looks like in the browser:

The ProcessForm() action that does this detection is shown below:

[HttpPost]
public ActionResult ProcessForm(Customer obj,
                   string save,string cancel)
{
    if(!string.IsNullOrEmpty(save))
    {
        ViewBag.Message = "Customer saved successfully!";
    }
    if (!string.IsNullOrEmpty(cancel))
    {
        ViewBag.Message = "The operation was cancelled!";
    }
    return View("Result",obj);
}
 
Notice that the ProcessForm() action receives three parameters through model binding. The first parameter is the Customer object that holds the values entered in the textboxes. The save and cancel string parameters hold the value of the respective buttons. These parameter names must match the names of the butttons on the form. If a button is clicked, its value is received in the action. Otherwise its value will be null. For example, if you click on the Save button the save parameter will hold Save and the cancel parameter will hold null.
The if blocks then check the value of save and cancel parameters and accordingly set a ViewBag property. The ProcessForm() action returns Result view. The markup of Result view is shown below:

@model MultipleSubmitButtons.Models.Customer
...

    

@ViewBag.Message

@Html.DisplayForModel()
 
The Result view simply displays the Message property from the ViewBag and also displays the value held in the Customer object.

2. Multiple buttons with the same name

This technique is similar to the previous technique. But the buttons involved are given the same name in the HTML markup.

@using (Html.BeginForm("ProcessForm", "Home", FormMethod.Post))
{
    @Html.EditorForModel()
    

    < input type="submit" name="submit" value="Save" />
    < input type="submit" name="submit" value="Cancel" />
}
 
Notice that the name attribute of both the buttons is set to submit and their value attribute is set to some string.
Then the ProcessForm action accepts a single parameter - submit - that receives the value of the button clicked by the user. This is shown below:

[HttpPost]
public ActionResult ProcessForm(Customer obj, string submit)
{
    switch(submit)
    {
        case "Save":
            ViewBag.Message = "Customer saved successfully!";
            break;
        case "Cancel":
            ViewBag.Message = "The operation was cancelled!";
            break;
    }
    return View("Result", obj);
}
 
As you can see the submit parameter is checked for its value. If you click the Save button, submit parameter will be Save. If you click on Cancel button the submit parameter will have alue of Cancel. The switch statement then checks the values and does some processing specific to a button.

3. HTML5 formaction and formmethod attributes

In this technique you use the formaction and formmethod attributes introduced in HTML5. These attributes can be set on the buttons under consideration. The formaction attribute indicates the form's action whereas the formpost attribute indicates the form submition method.
The modified form markup is shown below:

@using (Html.BeginForm())
{
    @Html.EditorForModel() 
 
    < input type="submit" name="save" value="Save" 
              formaction="SaveForm" formmethod="post" />
    < input type="submit" name="cancel" value="Cancel" 
              formaction="CancelForm" formmethod="post" />
 }
 
As you can see the formaction attribute of the save button submits to SaveForm() action whereas the cancel button submits to the CancelForm() action. The formmethod attribute is set to post for both the buttons.
The SaveForm() and CancelForm() actions are shown below:

[HttpPost]
public ActionResult SaveForm(Customer obj)
{
    ViewBag.Message = "Customer saved successfully!";
    return View("Result", obj);
}

[HttpPost]
public ActionResult CancelForm(Customer obj)
{
    ViewBag.Message = "The operation was cancelled!";
    return View("Result", obj);
}
 
The SaveForm() needs to accept only the Customer model object because this is exclusively called by the save button. Similarly the CancelForm() action needs to receive only the model object since it is invoked exclusively by the cancel button.

4. jQuery / JavaScript code

If above techniques doesn't meet your requirements you can always use jQuery or JavaScript to programatically set the form action. The following markup shows how the modified form looks like:

@using (Html.BeginForm())
{
    @Html.EditorForModel()
    
    < input type="submit" id="save" value="Save" />
    < input type="submit" id="cancel" value="Cancel" />
    
}
 
This time the ID attribute of the buttons is set to save and cancel respectively. This way you can easily access them in the jQuery code. The jQuery code that wires the click event handlers for these buttons and sets the form action dynamically is shown below:

$(document).ready(function () {

    $("#save").click(function () {
        $("form").attr("action", "/home/saveform");
    });

    $("#cancel").click(function () {
        $("form").attr("action", "/home/cancelform");
    });

});
 
The click event handler of the save button sets the action attribute of the form to /home/saveform. This is done using the attr() method of jQuery. On the same lines the click event handler of the cancel button sets the action to /home/cancelform.

Which technique to use?

All the techniques discussed above are standard ways of dealing with multiple submit buttons. So, the selection depends on developers choice and application requirements. If your logic is such that a single action is doing multiple things then technique #1 comes handy. An example of this technique can be seen here that develops a wizard with next and previous buttons. The second technique has a drawback that if you change the value attribute then you need to adjust the switch statement accordingly. Moreover, it may pose a problem in multilingual websites since you are checking the value displayed on the buttons. So, this technique should be avoided if you anticipate the button texts to change. If the target browser supports HTML5 (all the leading browsers support these attributes) then using formaction and formmethod attributes is recommended. This way you get a cleaner separatation of the concerns and the code is more readable. If the form or the buttons themselves are being generated on-the-fly then jQuery technique is handy because things are changing dynamically.