Tag Archives: Asp.Net MVC2

Asp.Net MVC Code

How to POST JSON data to an Asp.Net MVC 2 app with ValidateAntiForgeryToken

The title says it all really, earlier this week I was attempting to submit data in the form of JSON to a controller method that implemented the ValidateAntiForgeryTokenAttribute.

On the client side of things  I managed to get the validation input generated by Html.AntiForgeryToken into my JSON object but I just kept on getting the “A required anti-forgery token was not supplied or was invalid” exception.

After a bit of research I found the problem to lie in the Asp.Net MVC 2 source, if you look at the ValidateAntiForgeryTokenAttribute file specifically on line 56 (OnAuthorization method) you’ll see this:

string formValue = filterContext.HttpContext.Request.Form[fieldName];

The validation class always checks the form for the token but we don’t have any form data when we post JSON!

So my solution was to modify like so:

      string value;
      if (filterContext.HttpContext.Request.ContentType.ToLower().Contains("json"))
      {
        var bytes = new byte[filterContext.HttpContext.Request.InputStream.Length];
        filterContext.HttpContext.Request.InputStream.Read(bytes, 0, bytes.Length);
        filterContext.HttpContext.Request.InputStream.Position = 0;
        var json = Encoding.ASCII.GetString(bytes);
        var jsonObject = JObject.Parse(json);
        value = (string)jsonObject[fieldName];
      }
      else
      {
        value = filterContext.HttpContext.Request.Form[fieldName];
      }

What happens here is I do a check to see if the post is json, if so I then pull the string from the input stream and using a JSON parser extract the token.

Since we’re modifying the ValidateAntiForgery class we might as allow it to work  with (or restrict to) any http verb.

This is what I’ve ended up with (sorry about the formatting):

namespace Your.App
{
  using System;
  using System.Linq;
  using System.Text;
  using System.Web;
  using System.Web.Mvc;
  using Newtonsoft.Json.Linq;

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
  public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
  {
    private string _salt;
    private AntiForgeryDataSerializer _serializer;
    private readonly AcceptVerbsAttribute _verbs;

    public string Salt
    {
      get
      {
        return _salt ?? String.Empty;
      }
      set
      {
        _salt = value;
      }
    }

    internal AntiForgeryDataSerializer Serializer
    {
      get
      {
        if (_serializer == null)
        {
          _serializer = new AntiForgeryDataSerializer();
        }
        return _serializer;
      }
      set
      {
        _serializer = value;
      }
    }

    public ValidateJsonAntiForgeryTokenAttribute(HttpVerbs verbs = HttpVerbs.Post):this(null, verbs)
    {
    }

    public ValidateJsonAntiForgeryTokenAttribute(string salt, HttpVerbs verbs = HttpVerbs.Post)
    {
      this._verbs = new AcceptVerbsAttribute(verbs);
      this._salt = salt;
    }

    private bool ValidateFormToken(AntiForgeryData token)
    {
      return (String.Equals(Salt, token.Salt, StringComparison.Ordinal));
    }

    private static HttpAntiForgeryException CreateValidationException()
    {
      return new HttpAntiForgeryException("A required anti-forgery token was not supplied or was invalid.");
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
      if (filterContext == null)
      {
        throw new ArgumentNullException("filterContext");
      }

      // We only need to validate this if it's a post
      string httpMethodOverride = filterContext.HttpContext.Request.GetHttpMethodOverride();
      if (!this._verbs.Verbs.Contains(httpMethodOverride, StringComparer.OrdinalIgnoreCase))
      {
        return;
      }

      string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
      string cookieName = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);

      HttpCookie cookie = filterContext.HttpContext.Request.Cookies[cookieName];
      if (cookie == null || String.IsNullOrEmpty(cookie.Value))
      {
        // error: cookie token is missing
        throw CreateValidationException();
      }
      AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

      string value;
      if (filterContext.HttpContext.Request.ContentType.ToLower().Contains("json"))
      {
        var bytes = new byte[filterContext.HttpContext.Request.InputStream.Length];
        filterContext.HttpContext.Request.InputStream.Read(bytes, 0, bytes.Length);
        filterContext.HttpContext.Request.InputStream.Position = 0;
        var json = Encoding.ASCII.GetString(bytes);
        var jsonObject = JObject.Parse(json);
        value = (string)jsonObject[fieldName];
      }
      else
      {
        value = filterContext.HttpContext.Request.Form[fieldName];
      }

      if (String.IsNullOrEmpty(value))
      {
        // error: form token is missing
        throw CreateValidationException();
      }
      AntiForgeryData formToken = Serializer.Deserialize(value);

      if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal))
      {
        // error: form token does not match cookie token
        throw CreateValidationException();
      }

      if (!ValidateFormToken(formToken))
      {
        // error: custom validation failed
        throw CreateValidationException();
      }
    }
  }
}

Also I had to copy over 2 classes from the MVC source. The “AntiForgeryData” and “AntiForgeryDataSerializer” since they are interal and my project wouldn’t compile without them.

Final note, if you’re having trouble with the Serializer you copied over (in the Deserialize method and casting to a Triplet) then this may be of some help (.Net 4.0 only):

                dynamic deserializedObj = formatter.Deserialize(serializedToken);
                return new AntiForgeryData() {
                    Salt = deserializedObj[0],
                    Value = deserializedObj[1],
                    CreationDate = (DateTime)deserializedObj[2]
                };
Asp.Net MVC

Minify CSS & JS With MVCContrib

I was doing a bit of research recently trying to find a way to compress, combine and minify css and js in Asp.Net MVC 2 and found a few ideas on the subject. The one that caught my eye was the MvcContrib IncludeHandling; a little hidden gem.

Getting it up a running however wasn’t so straight forward (there isn’t much documentation about it, or at least not much I could find.). The first problem I stumbled upon was the HtmlExtensions (RenderIncludes function) required a dependency resolver which the project I was on didn’t use. To get round this I simply copied the source (from the HtmlExtensions link) and replaced the dependency resolver with the projects IoC framework (in this case we’re using MvcTurbine with Ninject)

Next you need to register the dependency with your IoC of choice, like I said previously we’re using MvcTurbine so this is how I did with with a service registration.

namespace YourProject.MvcSite.Registration
{
  using System.Web;
  using MvcContrib;
  using MvcContrib.IncludeHandling;
  using MvcContrib.IncludeHandling.Configuration;
  using MvcTurbine.ComponentModel;

  /// <summary>
  /// Regisers the minify interfaces
  /// </summary>
  public class MinifyRegistration  : IServiceRegistration 
  {
    /// <summary>
    /// Registers the components with the specified <see cref="T:MvcTurbine.ComponentModel.IServiceLocator"/> instance.
    /// </summary>
    /// <param name="locator">Instance of <see cref="T:MvcTurbine.ComponentModel.IServiceLocator"/> to use.</param>
    public void Register(IServiceLocator locator)
    {
      var httpContext = new HttpContextProvider(HttpContext.Current);
      var handler = new IncludeHandlingSectionHandler();
      var reader = new IncludeReader(httpContext);
      var storage = new StaticIncludeStorage(new KeyGenerator());
 
      locator.Register<IIncludeHandlingSettings>(handler);
      locator.Register<IIncludeCombiner>(new IncludeCombiner(handler, reader, storage, httpContext));
    }
  }
}

The final step is to select the js/css files you want and then combine them. In the example below we’re using the T4MVC template to generate the static file links which and the code is used in a view (in our example it’s the site master view).

    <%
      var scripts = new List<string>
                      {
                        "~" + Links.Scripts.jquery_1_4_2_min_js,
                        "~" + Links.Scripts.jquery_ui_1_8_2_custom_min_js,
                        "~" + Links.Scripts.jquery_localscroll_1_2_7_min_js,
                        "~" + Links.Scripts.jquery_scrollTo_1_4_2_min_js,
                        "~" + Links.Scripts.jquery_serialScroll_1_2_2_min_js,
                        "~" + Links.Scripts.Ajax_js,
                        "~" + Links.Scripts.Custom_js,
                        "~" + Links.Scripts.json2_js,
                        "~" + Links.Scripts.flowplayer_3_2_3_min_js,
                        "~" + Links.Scripts.DragDrop_js,
                        "~" + Links.Scripts.Slider_js
                      };
    %>

    <%=Html.RenderJs(scripts)%>
Code

Asp.Net MVC 2 Routing SubDomains to Areas

I’ve been building an Asp.Net MVC 2 site with Tom on the new Thap site and we hit a stumbling point regarding sub-domains and areas; you can probably guess what the problem was from the title.

Any way after a bit of googling it looks like no one has figured this out, or that they arn’t sharing. So it’s time I shared the solution that worked for us. This requires no libraries or esoteric settings or anything like that, just a little bit of code that will end up making your routes look like this:

context.Routes.MapSubDomainRoute(
        "Admin_default", // Name
        "admin", // SubDomain
        "{controller}/{action}/{id}", // Url
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Defaults
        new[] { typeof(Controllers.HomeController).Namespace }); // Namespace

First we need to create a Route class that can handle subdomains, lucky for you I just happen to have one. What this class does is check the incoming request, if the sub-domain matches it then checks to see if the rest of the url matches the route you specified:

Update: Since publishing this post I’ve added an update to the code. The GetVirtualPath function  has been overridden to check if the area of the value matches the sub-domain . This was needed because it messed up the url generation for everything.

namespace Your.App
{
  using System.Web;
  using System.Web.Routing;

  /// <summary>
  /// A route class to work with a specific subDomain
  /// </summary>
  public class SubDomainRoute : Route
  {
    /// <summary>
    /// The subDomain to route against
    /// </summary>
    private readonly string subDomain;

    /// <summary>
    /// Initializes a new instance of the <see cref="SubDomainRoute"/> class.
    /// </summary>
    /// <param name="subDomain">The sub domain.</param>
    /// <param name="url">The URL.</param>
    /// <param name="routeHandler">The route handler.</param>
    public SubDomainRoute(string subDomain, string url, IRouteHandler routeHandler) : base(url, routeHandler)
    {
      this.subDomain = subDomain.ToLower();
    }

    /// <summary>
    /// Returns information about the requested route.
    /// </summary>
    /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
    /// <returns>
    /// An object that contains the values from the route definition.
    /// </returns>
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
      var url = httpContext.Request.Headers["HOST"];
      var index = url.IndexOf(".");

      if (index < 0)
      {
        return null;
      }

      var possibleSubDomain = url.Substring(0, index).ToLower();

      if (possibleSubDomain == subDomain)
      {
        var result =  base.GetRouteData(httpContext);
        return result;
      }

      return null;
    }

    /// <summary>
    /// Returns information about the URL that is associated with the route.
    /// </summary>
    /// <param name="requestContext">An object that encapsulates information about the requested route.</param>
    /// <param name="values">An object that contains the parameters for a route.</param>
    /// <returns>
    /// An object that contains information about the URL that is associated with the route.
    /// </returns>
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
      // Checks if the area to generate the route against is this same as the subdomain
      // If so we remove the area value so it won't be added to the URL as a query parameter
      if(values != null && values.ContainsKey("Area"))
      {
        if(values["Area"].ToString().ToLower() == this.subDomain)
        {
          values.Remove("Area");
          return base.GetVirtualPath(requestContext, values);
        }
      }

      return null;
    }
  }
}

The next step we take is to create a bunch of extensions methods to make mapping the sub-domain a bit easier. I lifted this code straight from the MVC source and made a tiny adjustment.

namespace Your.App
{
  using System;
  using System.Diagnostics.CodeAnalysis;
  using System.Web.Mvc;
  using System.Web.Routing;

  public static class RouteCollectionExtensions
  {
    [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",
        Justification = "This is not a regular URL as it may contain special routing characters.")]
    public static Route MapSubDomainRoute(this RouteCollection routes, string name, string subDomain, string url)
    {
      return MapSubDomainRoute(routes, name, subDomain, url, null /* defaults */, (object)null /* constraints */);
    }

    [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",
        Justification = "This is not a regular URL as it may contain special routing characters.")]
    public static Route MapSubDomainRoute(this RouteCollection routes, string name, string subDomain, string url, object defaults)
    {
      return MapSubDomainRoute(routes, name, subDomain, url, defaults, (object)null /* constraints */);
    }

    [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",
        Justification = "This is not a regular URL as it may contain special routing characters.")]
    public static Route MapSubDomainRoute(this RouteCollection routes, string name, string subDomain, string url, object defaults, object constraints)
    {
      return MapSubDomainRoute(routes, name, subDomain, url, defaults, constraints, null /* namespaces */);
    }

    [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",
        Justification = "This is not a regular URL as it may contain special routing characters.")]
    public static Route MapSubDomainRoute(this RouteCollection routes, string name, string subDomain, string url, string[] namespaces)
    {
      return MapSubDomainRoute(routes, name, subDomain, url, null /* defaults */, null /* constraints */, namespaces);
    }

    [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",
        Justification = "This is not a regular URL as it may contain special routing characters.")]
    public static Route MapSubDomainRoute(this RouteCollection routes, string name, string subDomain, string url, object defaults, string[] namespaces)
    {
      return MapSubDomainRoute(routes, name, subDomain, url, defaults, null /* constraints */, namespaces);
    }

    [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#",
        Justification = "This is not a regular URL as it may contain special routing characters.")]
    public static Route MapSubDomainRoute(this RouteCollection routes, string name, string subDomain, string url, object defaults, object constraints, string[] namespaces)
    {
      if (routes == null)
      {
        throw new ArgumentNullException("routes");
      }
      if (url == null)
      {
        throw new ArgumentNullException("url");
      }
      if (subDomain == null)
      {
        throw new ArgumentNullException("subDomain");
      }

      Route route = new SubDomainRoute(subDomain, url, new MvcRouteHandler())
      {
        Defaults = new RouteValueDictionary(defaults),
        Constraints = new RouteValueDictionary(constraints)
      };

      if ((namespaces != null) && (namespaces.Length > 0))
      {
        route.DataTokens = new RouteValueDictionary();
        route.DataTokens["Namespaces"] = namespaces;
      }

      routes.Add(name, route);

      return route;
    }
  }
}

The final thing to do is set up your routes in the AreaRegistration class

context.Routes.MapSubDomainRoute(
        "Admin_default", // Name
        "admin", // SubDomain
        "{controller}/{action}/{id}", // Url
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Defaults
        new[] { typeof(Controllers.HomeController).Namespace }); // Namespace

Look familiar?

The namespace section is used to distinguish between Identically name controllers, so if you have a home controller in two areas that line will stop any conflicts.

There is one last item to take note of, when you create a new action in a controller of an area you’ll need to specify the location of the view. If your using the MvcContrib like we do then the T4MVC template comes in vary handy. All the Actions in the areas now look something like this:

    public virtual ActionResult Index()
        {
            return View(this.Views.Index);
        }

Or if you’re not using the T4MVC then this:

    public virtual ActionResult Index()
        {
            return View("~/Areas/Admin/Views/Home/Index.aspx");
        }