ASP.NET MVC working smart with cookies – Custom ValueProvider

ValueProviders are the mechanism within the MVC framework that tries to take information from the HTTP Request and then resolve parameter values on action methods. Complex as well as simple types will be resolved using one of the four inbuilt value providers listed.

  • FormValueProvider: Provides values from posted form parameters.
  • QueryStringValueProvider: Provides values from the query string collection.
  • HttpFileCollectionValueProvider: Provides values from posted files.
  • RouteDataValueProvider: Provides values from route parameters.

Below I have created a controller which has one action. When the Index action is called using GET a cookie is added to the browser called isRegistered with the value “true” assigned to it. When the form is posted the index action is again called and the cookie value is automatically assigned to the isRegistered parameter on the POST index method. But how?  By using a custom Value Provider!

    public class PersonController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            AddCookie();
            ViewData["CookieData"] = false;
            return View(new Employee { CreateDate = new DateTime(2010, 10, 10) });
        }
        [HttpPost]
        public ActionResult Index(Employee employee, bool isRegistered)
        {
            ViewData["CookieData"] = isRegistered;
if (!ModelState.IsValid) return View(employee); return View(employee); } private void AddCookie() { HttpContext.Response.Cookies.Add( new HttpCookie("isRegistered", "true") ); } }

Below I have the mark-up for a checkbox that looks at the value assigned to the ViewData key “CookieValue”.

            <div>
                <label>Registered</label>
                <span><%=Html.CheckBox("Registered", (bool)ViewData["CookieData"])%></span><br />
            </div>

When the form is requested the registered checkbox is unchecked. After the form is posted to the server the registered checkbox is set to checked as the CookieValueProvider resolves the value isRegistered by looking at all the cookies stored for the site.  (Note: the registered checkbox does not have an ID or NAME so it is not resolved by the FormValueProvider)

image 
Below is an implementation of the CookieValueProviderFactory and also CookieValueProvider. By convention when the CookieValueProvider is invoked by the framework it will try to match the name of variable passed to it from the action method signature to a cookie with the same name. If a match is found the variables value will be set.

    public class CookieValueProviderFactory : ValueProviderFactory
    {
        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
                return new CookieValueProvider(controllerContext.HttpContext.Request.Cookies);
        }
        private class CookieValueProvider : IValueProvider
        {
            private readonly HttpCookieCollection _cookieCollection;

            public CookieValueProvider(HttpCookieCollection cookieCollection)
            {
                _cookieCollection = cookieCollection;
            }

            public bool ContainsPrefix(string prefix)
            {
                return _cookieCollection[prefix] != null;
            }

            public ValueProviderResult GetValue(string key)
            {
                HttpCookie cookie = _cookieCollection[key];
                return cookie != null ? 
                                      new ValueProviderResult(cookie.Value, 
                                                              cookie.Value, 
                                                              CultureInfo.CurrentUICulture) 
                                      : null;
            }
        }
    }

All that needs to be done now is for the CookieValueProviderFactory to be added to the ValueProviderFactories collection via the Global.asax.  The framework will now search through five all five value providers in order to set parameters on action methods. Please be aware that this is done in order so if the value of isRegistered is set by the QueryStringValueProvider then the CookieValueProvider would not be used to set the value of isRegistered on the action method.

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Person", action = "Index", id = UrlParameter.Optional } 
            );
        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
            ValueProviderFactories.Factories.Add(new CookieValueProviderFactory());
        }
    }

The implementation above will work with MVC 2.0 but not 1.0 as the ASP.NET MVC team changed how model binding is implemented between the framework versions.

Sample Code: Download

Twitter Twitter Twitter Twitter Twitter
March 6, 2010 20:52 by DanWatson

IIS7 Does Not Allow Cross Domain Calls In Full Trust Mode By Default – The Fix!

I have been working on a project recently where I have been making cross domain web requests to twitter using the Yedda.Net library.  Everything was working correctly when executing the source using Visual Studio and Cassini (Visual Studios built in web server) but when I made a trial deployment I ran into a security exception.

I found this very strange as when I deployed the source to a server running IIS6 everything functioned as expected but when deploying to a server running IIS7 I was faced with the following exception.

SecurityException: Request for the permission of type 'System.Net.WebPermission, System, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089' failed.

Both sites on the two different servers running IIS6 and IIS7 were set up to run in full trust so it didn’t make any sense that IIS7 would not allow the cross domain web request.

image

When the application was making the WebRequest the stack trace was showing that the application did not have enough permissions to execute the cross domain call to twitter. But I am running in full trust so now I am super confused (below is the source where the exception was thrown)!

        public TweetServiceModel GetLatestTweet()
        {
            Yedda.Twitter twitter = new Yedda.Twitter();
            XmlDocument document = twitter.ShowAsXML(TwitterConfiguration.Username, 
TwitterConfiguration.Password, TwitterConfiguration.Username); try { // ReSharper disable PossibleNullReferenceException string tweet = XDocument.Parse(document.InnerXml).Element("user")
.Element("status")
.Element("text").Value; return new TweetServiceModel(tweet); // ReSharper restore PossibleNullReferenceException } catch (Exception) { return new TweetServiceModel(); } }


The Fix!

It seems that the default behaviour for IIS7 is not to allow the WebRequest to execute even when running in full trust. To enable this behaviour go to yours sites application pool and click on advanced settings. Then change the Load User Profile setting to True instead of False.

imageimage

Twitter Twitter Twitter Twitter Twitter
February 27, 2010 20:08 by DanWatson