No RSVP required: MvcApplication_BeginRequest and MvcApplication_EndRequest

A few weeks ago I started a new project, and I needed to do some work before and after every request.  It’s a Web API project, so the methods I needed to tap into were MvcApplication_BeginRequest and MvcApplication_EndRequest.

Sounds simple, and usually it would be.  Except that this will be the first project I do in 100% C#.  While I’ve been a polyglot developer for the last few years, I’ve only used C# for the test suites.  VB has been my "goto" language for the actual application (I love language humor).  And while I’ve gotten pretty good at writing in C# and converting between C# and VB, there are still nooks and crannies to C# that cause me to stumble.  How to get ahold of these two event handlers became one of them.

I began with Application_Start.  I figured that would be the place to register the event handlers:

protected void Application_Start()
{
    this.BeginRequest += MvcApplication_BeginRequest;
    this.EndRequest += MvcApplication_EndRequest;

    // More start-up stuff here
}

protected void MvcApplication_BeginRequest(object sender, EventArgs e)
{
    // Do stuff
}

protected void MvcApplication_EndRequest(object sender, EventArgs e)
{
    // Do stuff
}

I hit F5, and was greeted with this error:

10

[NullReferenceException: Object reference not set to an instance of an object.]
    System.Web.PipelineModuleStepContainer.GetStepArray(RequestNotification notification, Boolean isPostEvent) + 25
    System.Web.PipelineStepManager.ResumeSteps(Exception error) +984
    System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb) +95
    System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +186

Well that’s a new one.  Consulting the internets turned up a couple of posts that showed me where I had gone astray:

http://stackoverflow.com/questions/6590578/how-do-you-wire-up-application-beginrequest-in-asp-net-mvc

http://www.west-wind.com/weblog/posts/2009/Jun/18/How-do-ASPNET-Application-Events-Work

Basically, explicitly adding the event handlers was unnecessary – in fact, it was downright counterproductive.  ASP.NET will find these handlers through Reflection – I didn’t need to wire them up at all.

Incidentally, when I removed the explicit wire-up for this.BeginRequest, but kept the one for this.EndRequest, the error message changed slightly:

20

[NullReferenceException: Object reference not set to an instance of an object.]
    System.Web.PipelineStepManager.ResumeSteps(Exception error) +224
    System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb) +95
    System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +186

Not the clearest exception messages on the planet, but thankfully this issue only set me back a few minutes.  Onward!

Advertisement

It’s all or nothing – MVC Action Attributes

Allow me to preface this by saying I knew how everything described in this post worked – at least on one level.  What dawned on me the other day was the underlying connection between these facts, a deeper understanding of how MVC works.  This post will fall into the bucket of "Mark was a little late to the enlightened party – again."

Let’s say I have a controller called "Home", with an action that looks like this:

    Function Index() As ActionResult
        Return View()
    End Function

By default, the controller actions are available using any HTTP verb.  In the above case, both "POST /home/index" and "GET /home/index" (or any other HTTP verb for that matter) will result in this controller action being invoked.  You can, however, explicitly declare that the method is only allowed for GETs (for example) by adding the HttpGet attribute to the action:

    <HttpGet()> _
    Function Index() As ActionResult
        Return View()
    End Function

Alternatively you can decorate the action with an attribute to make it respond only to an HTTP POST:

    <HttpPost()> _
    Function Index() As ActionResult
        Return View()
    End Function

By adding these attributes, you’re telling MVC when to invoke this particular action – “only on GETs” or “only on POSTs”.  So far so good.  Now, let’s say you have two different submit buttons on the Index view’s form. 

    <form action="/Home/Action" method="post">
        <input type="submit" id="Edit" name="Edit" value="Edit" />
        <input type="submit" id="Save" name="Save" value="Save" />
    </form>

How could you wire those two submit buttons up so that they would invoke two different controller actions, such as the following:

    <HttpPost()> _
    Function Edit() As ActionResult
        ViewData("Message") = "Edit clicked!"
        Return View("About")
    End Function

    <HttpPost()> _
    Function Save() As ActionResult
        ViewData("Message") = "Save clicked!"
        Return View("About")
    End Function

The problem here is that MVC doesn’t have a way to distinguish between these two actions.  They both configured to respond to HTTP POSTs.  You can’t change one of them to use HTTP GET because you will presumably be POSTing data in both cases.  MVC needs some other way to determine which action is appropriate.  Enter the HttpParamAction attribute, by Andrey Shchekin.  I won’t go into depth how this attribute works (Shchekin does a fine job of that), but here’s the 30-second explanation: it allows you to match the names of buttons on your form to the action methods in the controller, and therefore respond to those buttons individually.

So, let’s modify our methods’ attributes:

    <HttpPost()> _
    <HttpParamAction()> _
    Function Edit() As ActionResult
        ViewData("Message") = "Edit clicked!"
        Return View("About")
    End Function

    <HttpPost()> _
    <HttpParamAction()> _
    Function Save() As ActionResult
        ViewData("Message") = "Save clicked!"
        Return View("About")
    End Function


Now, when the user clicks the "Edit" button, the MVC framework starts by going through the actions defined in the controller looking for the one to invoke.  The HttpPost and HttpParamAction attributes work together to force MVC to only invoke the Edit() action if

  1. the form is being POSTed, and
  2. the "Edit" button was the one used to do the POSTing

Likewise with the Save() action and the "Save" button.

On a side-note, did you notice the form’s action attribute was defined as "/home/action"?  Where did the "action" come from?  It is just a made-up name.  The important thing to note is that whatever it is needs to match what is defined in the attribute itself.  In his post above, Shchekin mentions that his original solution just hard-coded the name of the action name into the attribute, but an improvement would be to pass that in as a parameter to the attribute.  The action name is basically another way to allow MVC to filter the incorrect methods out – if you changed the HTML to post to /home/blah, neither the Edit() nor the Save() methods would be matched. 

***

Now, let’s say I had a Home controller action that looked like this:

    <HttpGet()> _
    Function Detail(id As Long) As ActionResult
        ViewData("Message") = String.Format("Product ID {0} coming right up", id)
        Return View("Detail")
    End Function

That would accept requests such as

GET /Home/Detail?id=12345

When I do a GET on this URI, MVC maps the "id" querystring value to my method’s "ID" parameter, and all is well with the world.  But what if I wanted to also allow requests like this:

GET /Home/Detail?sku=9876545134&locale=en-us

My first instinct would be to create a second controller action, with those other parameters:

    <HttpGet()> _
    Function Detail(id As Long) As ActionResult
        ViewData("Message") = String.Format("Product ID {0} coming right up", id)
        Return View("Detail")
    End Function

    <HttpGet()> _
    Function Detail(sku As Long, locale As String) As ActionResult
        ViewData("Message") = String.Format("Product SKU {0} for Locale {1} coming right up", sku, locale)
        Return View("Detail")
    End Function

Here’s where I run into trouble.  With these two actions defined, both of these requests:

GET /Home/Detail?id=12345

GET /Home/Detail?sku=9876545134&locale=en-us

Result in what I call the "ambiguous" error, or more precisely, the AmbiguousMatchException:

The current request for action ‘Detail’ on controller type ‘HomeController’ is ambiguous between the following action methods:

System.Web.Mvc.ActionResult Detail(Int64) on type MvcApplication1.HomeController

System.Web.Mvc.ActionResult Detail(Int64, System.String) on type MvcApplication1.HomeController

What happened here?  MVC finds two different methods marked as HttpGet, but can’t figure out which one to invoke for the above requests.  If this were a "regular" class these would be two overloaded variants of the same method, and the .NET runtime would have no problem distinguishing them purely based on their signatures.

Ok, so what if I were to merge the two methods, so there was only one to find?

    <HttpGet()> _
    Function Detail(id As Long, sku As Long, locale As String) As ActionResult
        ViewData("Message") = String.Format("Product ID {0}, SKU {1} for Locale {2} coming right up", id, sku, locale)
        Return View("Detail")
    End Function

That brings up a different problem.  A request such as

    GET /Home/Detail?id=12345

Will fail with an ArgumentException:

The parameters dictionary contains a null entry for parameter ‘sku’ of non-nullable type ‘System.Int64’ for method ‘System.Web.Mvc.ActionResult Detail(Int64, Int64, System.String)’ in ‘MvcApplication1.HomeController’. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.

Parameter name: parameters

Objects of type "Long" are not nullable, but the request only included a parameter of "id".  There wasn’t anything passed in for the "sku", and since it wasn’t declared as "Nullable", it fails.  The "locale" parameter, being a string, is nullable, so this doesn’t cause any problems.

Likewise, a request for

    GET /Home/Detail?sku=9876545134&locale=en-us

Will fail with a similar exception:

The parameters dictionary contains a null entry for parameter ‘id’ of non-nullable type ‘System.Int64’ for method ‘System.Web.Mvc.ActionResult Detail(Int64, Int64, System.String)’ in ‘MvcApplication1.HomeController’. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.

Parameter name: parameters

Notice that the "id" property is referenced in this message, rather than the "sku" parameter.

Ok, so why not just make the two Long parameters nullable?

    <HttpGet()> _
    Function Detail(id As Long?, sku As Long?, locale As String) As ActionResult
        ViewData("Message") = String.Format("Product ID {0}, SKU {1} for Locale {2} coming right up", id, sku, locale)
        Return View("Detail")
    End Function

Well, that avoids the exceptions.  With both "id" and "sku" marked as "nullable" (notice the question marks after Long), both of our requests will go through, and the named parameters will be populated with data from the querystring.

However, this is a pretty poor design pattern, and you should avoid it.  With some parameters being populated for some requests, and other parameters for other requests, you have to manually examine the incoming data to figure out what to do.  There should be a better way to distinguish the requests.

And there is.

As this StackOverflow answer points out, you can overload MVC actions based on the parameters, you just have to do it through an attribute that looks at what gets included in the HTTP request, rather than the method signature.  The RequireRequestValue attribute allows us to force MVC to match based on a parameter passed in with the request.  If it doesn’t see the named parameter, then the current action being evaluated can’t be the "right" one.  With this in place, I can now decorate my methods to distinguish them:

    <HttpGet()> _
    <RequireRequestValue({"id"})> _
    Function Detail(id As Long) As ActionResult
        ViewData("Message") = String.Format("Product ID {0} coming right up", id)
        Return View("Detail")
    End Function

    <HttpGet()> _
    <RequireRequestValue({"sku", "locale"})> _
    Function Detail(sku As Long, locale As String) As ActionResult
        ViewData("Message") = String.Format("Product SKU {0} for Locale {1} coming right up", sku, locale)
        Return View("Detail")
    End Function

Full Disclosure: I did have to modify their solution a bit to allow the parameters to be named in the querystring (the original looked for the data in the route only).  Here is the VB-ported solution, with the modification:

Public Class RequireRequestValueAttribute
    Inherits ActionMethodSelectorAttribute

    Public Property ValueNames As String()

    Public Sub New(valueNames As String())
        Me.ValueNames = valueNames
    End Sub

    Public Overrides Function IsValidForRequest(controllerContext As System.Web.Mvc.ControllerContext, methodInfo As System.Reflection.MethodInfo) As Boolean
        Dim contains As Boolean = False
        For Each value As String In Me.ValueNames
            contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value) Or Not IsNothing(controllerContext.RequestContext.HttpContext.Request.QueryString.Item(value))
            If Not contains Then Exit For
        Next
        Return contains
    End Function

End Class

The modification is the addition of the "Or Not IsNothing(…)" clause in the "contains" variable assignment.  The first half looks for the parameters in the route itself (for example, if our routing allowed things like "GET /home/detail/{id}"), while the addition looks for these in the querystring.

With the addition of the RequireRequestValueAttribute attribute, we can now tell MVC how to distinguish between our overloaded variants.

Incidentally, adding these also means that a request like this:

GET /home/detail?blah=12132123

Will match neither variant, and unless you have a default route for "controller/action/" defined you’ll get a 404.

***

To sum up, then:

  1. MVC looks at the attributes on a controller action – not the method signatures – when it is trying to determine which one to invoke.
  2. You can stack the attributes on a controller action, and they will be AND-ed together.  Only if all conditions are met will that particular controller action be invoked by the MVC framework.

Now that I’ve joined the party, do you think I can get away with claiming that I was just "fashionably" late?

Yeah, ok, I get it.  You can stop laughing now.

Too much of a good thing: ASP.NET MVC 2 Validation

Last week I came across a real stress-inducing issue with MVC 2 validation.  I had a Required field validator on a model property, and it wasn’t actually requiring a value in the field.  Here are the steps to reproduce it:

  1. Created blank MVC 2 app
  2. Add a resource file to the project, and put in some strings
  3. Add a HomeController
  4. Add a model called "Demo" with three fields, all required.  For the error messages, have one with an embedded error message (specified using the “ErrorMessageString” property of the Required attribute), one that references the resource file, and a third that does both.
  5. Add a Home/Index.aspx view, strongly-typed and based on the Demo model.  I created this as a “Create” view, so all of the validation controls were dropped in for me.  I then trimmed it back a bit to keep it simple.
  6. Run the app, and try to submit the form with nothing in any of the fields. 

You’ll see the first two Required validators light up, but the third one is silent.  In fact, the model state for that third field is "valid", even when it’s empty.  It’s also “valid” even if I explicitly call TryValidateModel in the postback action.

Now, this is a fairly contrived example, but it gets to the heart of the issue.  Model properties with a Required attribute should be, you know, required.

This was driving me nuts.  I pulled in a colleague, Joshua, and after some tinkering he suggested taking out the ErrorMessageString property for the third model property.  Amazingly that did the trick.  With both the ErrorMessageString and ErrorMessageResourceName properties of the Required() attribute set, the Field3 validation will never fail.  This appears to be an MVC2 issue – if I try this with MVC 3, it throws this exception when I post the form back:

    System.InvalidOperationException: Either ErrorMessageString or ErrorMessageResourceName must be set, but not both.

I would have expected one or the other fields to have taken priority, but the MVC 3 designers went another way with it – just throw an error and force the programmer to stop hedging his bets and pick one.  Apparently, too much validation is a bad thing?  Who knew.

A complete sample application showing this can be found in the “MVC2Validation.zip” archive at http://TinyURL.com/MarkGilbertSource.

Properly Propertied Programming – ASP.NET MVC 2 Model Properties

The other day, I came across an interesting nuance of the ASP.NET MVC 2 framework.  I built a model class like the following:

Public Class ModelThatDoesntWork

    Public FieldA As String
    Public FieldB As String

End Class

What I meant to do was this, however:

Public Class ModelThatWorks

    Public Property FieldA As String
    Public Property FieldB As String

End Class

See the difference?  In the first, I’ve declared public members FieldA and FieldB, but in the second I’ve declared public properties.

As the name implies, the first class didn’t work, which in this case manifested in a couple of ways.  First, when I created a strongly-typed "Create" view based on ModelThatDoesntWork, none of the usual field controls (label, text box, and validation control) showed up in the markup.

That was odd.  Oh well, perhaps I needed to build the project before adding the view.  Maybe the MVC framework just didn’t see the new class members. 

I proceeded to add that markup by hand.  Adding the field controls didn’t turn up any warnings or errors, so I thought I was out of the woods.  When I tested posting data back from the form, however, I got my second surprise.  The model being posted back didn’t contain any of the data from the form.

    <HttpPost()> _
    Function ViewThatDoesntWork(MyModel As ModelThatDoesntWork) As ActionResult
        ' MyModel properties will be Nothing (since these are strings) - the MVC
        ' framework couldn't find the public members of the ModelThatDoesntWork 
        ' class.  It needs public properties
        Return View("ViewThatDoesntWork", MyModel)
    End Function

I’ve always had the hunch that under the hood, the MVC framework relied on reflection when it came to populating models on postback.  This issue seems to confirm that suspicion.  The framework is specifically looking for model properties.

Check out the MVCModels.zip archive on my SkyDrive at http://tinyurl.com/MarkGilbertSource for a sample application that shows these behaviors.

Dude, where’s my QueryString? MVC 2, RedirectToAction, and QueryString

In my most recent MVC 2 project, I was working on a gallery that had many "sub-galleries" – really just filtered versions of the full one.  I didn’t want to show the "full" gallery ever, so I wired up the Index.aspx action to redirect to my "default" sub-gallery.  If the user came in through a URL such as

http://blah.com/Gallery

Then it would take them to the default gallery, pre-filtered, which would have the same endpoint as if the user went here:

http://blah.com/Gallery/MyGallery

The "Index" action in the Gallery controller that handled this redirect originally looked like this:

Function Index() As ActionResult
    Return RedirectToAction("mygallery")
End Function

That worked great – until the requirements changed, that is.  A couple of weeks after I wrote this, we uncovered a need to pass in a custom querystring on the URL.  For example:

http://blah.com/Gallery?ProjectID=42

What I found is that RedirectToAction wasn’t preserving the querystring value at all.  It wasn’t just that the "ProjectID" value was gone – the parameter didn’t even exist.  Through some tinkering, I found that this URL

http://blah.com/Gallery/?ProjectID=42

was also dropping the querystring (notice the extra slash after Gallery), but this one

http://blah.com/Gallery/MyGallery?ProjectID=42

was working fine.  The latter made total sense – I was bypassing the RedirectToAction() method by going directly to the MyGallery() method.  I looked for a way to force RedirectToAction to preserve the querystring, but I couldn’t find anything.  So, I altered my tack.  Instead of using RedirectToAction() to go to MyGallery, I simply called the MyGallery() method directly from Index():

Function Index() As ActionResult
    Return MyGallery()
End Function

That worked.  I ended up on the correct view, and the querystring was preserved.

I’ve put together a small MVC 2 solution that illustrates this.  Download the "RedirectToAction Lost Querystring" archive from here: http://tinyurl.com/3qwvgo9.  The Home/Index page has some sample links that take you to Gallery/Index, and illustrate the above.  The solution requires Visual Studio 2010, and uses ASP.NET MVC 2.

One too many cookies – Visual Studio and FireCookie

My most recent project held most of the information tied to the current user’s session in cookies.  I needed to be able to pass information among the client logic, the server logic, and the Flash application hosted on the site.  Cookies seemed to be the common and easiest medium for that.  For the most part, that architectural decision turned out to be a good one, with a couple of exceptions.

The exceptions are the topic of this blog post and – of course – most of the issues I’ll describe were simply me learning how the world worked.

For the purposes of today’s post, I’ll use “Flash” to refer to the Flash application hosted in the site, “Client” to represent the client-side JavaScript logic, and “Server” to represent the ASP.NET MVC 2 server application.

 

Taking the red pill

Flash was hosted on the home page of the site, and we wanted it to display a special background image when the user visited the home page in a certain way.  If you visited the default home page, “/Home.aspx/Index”, Client would set a cookie called “background” that contained a default value, but only if the “background” cookie wasn’t already set by the Server.  Whatever value ended up in the cookie, Flash would see it and swap in the corresponding background image.  If, however, you visited one of the “themed” home pages, such as "/Home.aspx/Paper”, then Server would set the “background” cookie, thus preempting Client.  Once this cookie was set, as long as the user didn’t browse to a different themed home page, this cookie would persist during the session, and every subsequent request to the home page would have that background.

At least, that was the theory.  During some of our initial testing, we found that Flash was having problems displaying the correct background every time.  We eventually tracked it down to the value of the “background” cookie.

I ran the site through Visual Studio so I could step through the Server logic to see what was happening.  On the first request to /Home.aspx/Paper, the Server logic would set the “background” cookie in the Response.  The view would then render.  Then, on the next post back a second “background” cookie would appear in Request.Cookies. 

Excuse me?

Oh, and it got worse.  If that second request was for another themed page, my Server logic would add a new cookie to Response (as I had expected), but a copy of that same cookie would also be added to the Request.Cookies collection

What. The. Heck?!?

 

Down the rabbit hole

My first thought at this point was that for some reason, Server was not able to overwrite the cookies being set either by itself or by Client.  I spent several hours trying different methods of adding cookies before I finally came across a StackOverflow.com article that referenced the MSDN documentation on cookies:

ASP.NET includes two intrinsic cookie collections. The collection accessed through the Cookies collection of HttpRequest contains cookies transmitted by the client to the server in the Cookie header. The collection accessed through the Cookies collection of HttpResponse contains new cookies created on the server and transmitted to the client in the Set-Cookie header.

After you add a cookie by using the HttpResponse.Cookies collection, the cookie is immediately available in the HttpRequest.Cookies collection, even if the response has not been sent to the client.

(Emphasis mine; source: http://msdn.microsoft.com/en-us/library/system.web.httpresponse.cookies.aspx)

Ok, that at least explains why cookies were showing up in both Response.Cookies and Request.Cookies. 

With that past me, I turned my attention to why Request.Cookies still had two – one set by Client and one set by Server.  Through a lot more experimentation, I found that my Server cookies were, in fact, being overwritten, but in a round-about way.

Let’s get to the code.  To reproduce this I created an empty MVC 2 application, added a HomeController:

Public Class HomeController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        Return View()
    End Function

    <HttpPost()> _
    Function Index(ByVal SubmitButton As String) As ActionResult
        Dim ServerCookie As HttpCookie

        ServerCookie = New HttpCookie("background", String.Format("server-cookie-here: {0}", Now.Ticks))
        HttpContext.Response.Cookies.Add(ServerCookie)

        Return View()
    End Function

End Class

And the Home.aspx/Index view:

<%@ Page Language="VB" Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <script type="text/javascript">
        function setCookie(c_name, value, exdays) {
            var exdate = new Date();
            exdate.setDate(exdate.getDate() + exdays);
            var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
            document.cookie = c_name + "=" + c_value;
        }

        function getCookie(c_name) {
            var i, x, y, ARRcookies = document.cookie.split(";");
            for (i = 0; i < ARRcookies.length; i++) {
                x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
                y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
                x = x.replace(/^\s+|\s+$/g, "");
                if (x == c_name) {
                    return unescape(y);
                }
            }
        }

        if (getCookie("background") == null) {
            setCookie("background", "client-cookie-here", null);
            document.write("Client cookie set!");
        }
    </script>

    <div>
        <%Html.BeginForm()%>
            Click here to post back: <input type="submit" value="Submit" id="SubmitButton" />
        <%Html.EndForm()%>
    </div>
</body>
</html>

The setCookie and getCookie JavaScript functions used for this demo were pulled from http://www.w3schools.com/js/js_cookies.asp, and were NOT the actual methods I was using when I found this problem (we have a custom library for managing cookies).  However, the source problem ended up being unrelated to the specific Client cookie library.

Finally, I hacked the RegisterRoutes() function in Global.asax.vb to allow for this page to be served as Home.aspx/Index, which was the default home page for the site (again, this is an artifact of this sample which has no real bearing on the problem being analyzed; I include it here just for completeness):

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}")

    routes.MapRoute( _
        "Default", _
        "{*pathInfo}", _
        New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional} _
    )

    routes.MapRoute( _
        "CatchAll", _
        "{controller}/{action}/{id}", _
        New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional} _
    )

End Sub

This is enough logic to reproduce the error I was seeing.  I set a break point on the POST version of HomeController.Index(), and inspected the cookie collections:

image

Ignore the third and fourth watches – those will come into play later.  Notice that there are zero Response cookies set so far, and the only Request cookie is from the Client.  The Client cookie was set when the GET version of Index was executed.  So far so good.

Now, if I step through the rest of the Index action method, the Server cookie is added to both collections:

image

Both the Response and Request collections get the new Server cookie.

But why wasn’t the Client cookie being overwritten by the Server cookie in the Request collection?  Why are there two?  If I let the form make another round trip to the Client, things begin to get really hairy:

image

Here we have the form being posted back, but the new Server cookie hasn’t been set yet.  Notice that the Response collection is, again, zero, but there are still two cookies in Request.  If I step through the action method, that’s where the hair-pulling REALLY began:

image

My Response collection looks fine.

My Request collection, however, is just getting out of hand.  Not only do I have both the Client and the Server cookies, I have TWO Server cookies!

At this point, I was convinced that my logic for overwriting cookies was simply not working.  But, as I said before, those hours of research turned up nothing.  If I had just pressed on, I would have saved myself a bit of sanity.  When I let the form make yet another round trip to the Client, I get this:

image

A couple of things to notice here.  The first is that my Request.Cookies collection is back down to 2 cookies – one Client and one Server.  Awesome!  Also notice that the Server cookie returned has the later of the two timestamps shown for the Server cookies in the previous screenshot.  In other words, my Server logic is, in fact, overwriting the Server cookies.  Doubly-awesome!

Ok, so one puzzle solved.  Now I just have to figure out why my Server cookies are still not overwriting the Client one.

For this, I had to dig way, WAY back in my brain, back to my early days of working with the web.  I recalled that there were some other options when creating cookies that would allow them to be read or hidden from portions of your application.  Cookies were tied to the domain that created them, but even within the domain you could separate them out by a property called “path”.  Perhaps the path for these two cookies wasn’t identical, so the browser was treating them as separate creatures.  I checked the property in Studio:

image

Nuts.  Both cookies have the same path.  Well, it was a good try.  I examined the other properties on the two cookies, and couldn’t find anything else that should have been differentiating them.

By this point, I had been falling down this rabbit hole for over a day.  While I had made some clear progress at understanding how cookies were handled, I still hadn’t solved the core issue.  I decided to get another pair of eyes on this issue.  My colleague, Ron, obliged.

His first thought was to pull the site up in Firefox, and turn FireCookie loose on it.  He, too, wanted to examine the cookies, but came at it from a completely different angle than I had.  That turned out to make all the difference in the world.  Here’s what these two cookies looked like in FireCookie:

image

You’ve GOT to be kidding me.  The Path properties WERE different after all.  Visual Studio wasn’t reporting the Client cookie’s Path correctly!

Armed with that information, the solution was easy.  I simply modified the Client cookie logic to explicitly set the Path property to “/”:

function setCookie(c_name, value, exdays) {
   var exdate = new Date();
   exdate.setDate(exdate.getDate() + exdays);
   var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString()) + "; path=/";
   document.cookie = c_name + "=" + c_value;
}

Notice the addition of “path=/” to the end of the c_value variable. Once I did that, my Client and Server “background” cookies could now be viewed as one and the same. That allowed them to overwrite, and preempt each other.

 

Emerging from the rabbit hole

Beyond learning how the Request and Response cookie collections work, I learned a couple of valuable lessons here, both regarding the Visual Studio debugger.  The first is that is that a round-trip to the browser is apparently required for the debugger to de-dup the list of cookies shown in the Request.Cookies collection.  That was terribly confusing for me.  I was expecting to see the new Server cookie simply overwrite the old one – without having to go down to the browser to do it.

The other lesson learned is that I can’t trust the debugger to accurately report the “Path” property for cookies (whether it can’t report the correct Path for some reason, or it is simply a bug, it’s confusing either way).  I need to use a client-side tool like FireCookie to do that.

I just can’t decide – Missing Parameters and MVC Routing

Recently, I started seeing ELMAHs for one of my projects that suggested it was being called without a required ID in the request.  After playing with it for a bit, I found that that was indeed the case.  Valid requests look like this:

http://MySite.com/Home.aspx/Index/42

But I was getting requests that looked like this:

http://MySite.com/Home.aspx/Index

Up to this point my route for the valid requests had been this:

routes.MapRoute( _
    "DefaultWithSongID", _
    "home.aspx/index/{SongID}", _
    New With {.controller = "Home", .action = "Index", .SongID = UrlParameter.Optional} _
)

And my Index controller action looked like this:

Function Index(ByVal SongID As Long) As ActionResult
    ...
End Function

My first attempt was to define a second route – one that left the SongID out, but would still be handled by my controller action:

routes.MapRoute( _
    "DefaultNoSongID", _
    "home.aspx/index", _
    New With {.controller = "Home", .action = "Index", .SongID = 0} _
)

Had this worked, my next step was to handle the “0” case in the action, but I never made it that far.  When I fired up the solution in the debugger, I was greeted with this:

The current request for action ‘Index’ on controller type ‘HomeController’ is ambiguous between the following action methods:

System.Web.Mvc.ActionResult Index() on type RoutingSample.HomeController

System.Web.Mvc.ActionResult Index(Int64) on type RoutingSample.HomeController

Hmm.  Ok.  What if I left the .SongID parameter out of the declaration altogether?

routes.MapRoute( _
    "DefaultNoSongID", _
    "home.aspx/index", _
    New With {.controller = "Home", .action = "Index"} _
)

Nope.  No dice.  Same error.  So, back to the drawing board.

I reasoned that the error was due to the multiple routes in the routing table that referenced the Index action of the Home controller, so the solution seemed to require that I handle both cases  – with and without SongID on the URL – in a single route.  Here’s what I came up with for that revised route:

routes.MapRoute( _
    "DefaultWithSongID", _
    "home.aspx/index/{SongID}", _
    New With {.controller = "Home", .action = "Index", .SongID = UrlParameter.Optional} _
)

Why Mark, this looks suspiciously like the original route.  Why yes, my dear reader, it DOES look like the original one, doesn’t it.  Ahem.

With SongID back to being defined as “optional”, I went back to just a single Index method, and tried to simply handle the “missing SongID” case:

    Function Index(ByVal SongID As Long) As ActionResult
        If (SongID = 0) Then Return View("index", "site", "No Song ID used")
        Return View("index", "site", String.Format("Song ID {0} used", SongID.ToString))
    End Function

Unfortunately, that didn’t work either.  I got this error when the page was requested:

The parameters dictionary contains a null entry for parameter ‘SongID’ of non-nullable type ‘System.Int64’ for method ‘System.Web.Mvc.ActionResult Index(Int64)’ in ‘RoutingSample.HomeController’. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.

Parameter name: parameters

This was, I believe, the error that had me troubleshooting this to begin with.  Awesome – I‘m now coding in circles.

Ok, let’s think about this.  Normally, assigning “Nothing” to a variable of type Long results in it being set to 0.  MVC, though, was apparently having problems doing that implicit cast, so I tried modifying my controller action to take a nullable Long:

    Function Index(ByVal SongID As Long?) As ActionResult
        If (IsNothing(SongID)) Then Return View("index", "site", "No Song ID used")
        Return View("index", "site", String.Format("Song ID {0} used", SongID.ToString))
    End Function

As you can see, I also had to modify the check from “SongID = 0” to “IsNothing(SongID)”.  Those two changes allowed it to work.  I could now request Home.aspx/Index and Home.aspx/Index/42, and both would be handled correctly without errors.

Now, checking for the missing SongID in the controller action isn’t a horrible solution, but what would have been a little nicer is to be able to define a route where “SongID” was not present at all, and be able to map that route to a controller action – all from the Global.asax.  After I put the above solution into place, I did some additional digging on the internets to see if I was truly missing something.  What I found was this post by Robert Koritnik, where he explains why this scenario occurs, and how to write a custom “action method selector attribute”: http://erraticdev.blogspot.com/2011/02/improving-aspnet-mvc-maintainability.html.  This attribute basically acts as a hint for MVC to find the correct action method based on the parameters present in the request.

I liked this solution and Koritnik explains with much more clarity than I have why the nullable approach is a less than desirable solution.  However, by this point I had run out of time to implement anything else, so my “nullable” approach would have to stand.

Sigh.  I hate it when reality gets in the way of perfection.

It’s a Long shot – ASP.NET Routing and Enumeration Values

Now that the actual development frenzy has started to subside for my latest MVC project, I can start working on the backlog of technical posts related to various issues we encountered.  Today’s involves ASP.NET Routing.  I wanted to establish multiple versions of a gallery page, each filtered to a specific occasion (weddings, birthdays, etc.).  The URLs would look like these:

Gallery.aspx/just-because

Gallery.aspx/weddings

Gallery.aspx/birthdays

I had already set up an enumeration that held the values I wanted to use.  Behind the scenes, the numeric values for these items corresponded to IDs in the database:

Public Enum Specials As Long
    JustBecause = 1
    Weddings = 2
    Birthdays = 3
End Enum

I then proceeded to established my routes in my Global.asax/Application_Start handler (just the first rule is shown):

routes.MapRoute( _
    "Gallery-JustBecause", _
    "Gallery.aspx/just-because", _
    New With {.controller = "Gallery", .action = "ByOccasion", .id = Occasion.Specials.JustBecause} _
)

When I tried to fire up the site, however, I got this error:

The parameters dictionary contains a null entry for parameter ‘id’ of non-nullable type ‘System.Int64’ for method ‘System.Web.Mvc.ActionResult Index(Int64)’ in ‘MyProject.GalleryController’. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.

Parameter name: parameters

I tried replacing “Occasion.Specials.JustBecause” with the number “1”, and it worked fine.  For whatever reason, even though my enumeration was of type Long under the hood, Routing was not buying it.  However, manually casting the original enumeration value to a Long did the trick:

routes.MapRoute( _
    "Gallery-JustBecause", _ 
"Gallery.aspx/just-because", _
New With {.controller = "Gallery", .action = "ByOccasion", .id = CType(Occasion.Specials.JustBecause, Long)} _ )

And there was much rejoicing.

What is YOUR quest? ASP.NET MVC 2 Model Binding and Conditional Validation

In my MVC project, I had a model object that had a couple of foreign keys.  These were represented on the form as drop down lists that returned the ID of the item selected.  The one that was causing all of the problems was "Status", so I’ll simply refer to that one from here on out.

The form would post the ID of the Status selected.  My model class, however, wasn’t expecting an ID, though – it was a Castle ActiveRecord object, so it wanted an object of type "Status".  After a few failed attempts to try to convert the ID to an object in the controller action, I finally stumbled onto using a custom model binder.  This was a class that you attach to your model object (via a class-level attribute), and provides methods you can override to perform custom binding.

So, first, a simplified version of my model class:

 

Imports System.ComponentModel.DataAnnotations

<ModelBinder(GetType(EventBinder))> _
Public Class EventDetail

    Private _CurrentEvent As BusinessServices.CorporateEvent

    Public ReadOnly Property ID() As Long
        Get
            If (IsNothing(Me.CurrentEvent)) Then Return 0
            Return Me.CurrentEvent.ID
        End Get
    End Property

    Public Property CurrentEvent() As BusinessServices.CorporateEvent
        Get
            Return Me._CurrentEvent
        End Get
        Set(ByVal value As CorporateEvent)
            Me._CurrentEvent = value
        End Set
    End Property
End Class


And the initial model binder I used:

 

Public Class EventBinder
    Inherits DefaultModelBinder

    Public Overrides Function BindModel(ByVal controllerContext As System.Web.Mvc.ControllerContext, ByVal bindingContext As System.Web.Mvc.ModelBindingContext) As Object
        Dim MyModel As EventDetail

        MyModel = CType(MyBase.BindModel(controllerContext, bindingContext), EventDetail)

        If (controllerContext.HttpContext.Request.Form.AllKeys.Contains("CurrentEvent.EventStatus.StatusID")) Then
            MyModel.CurrentEvent.EventStatus = Status.GetByID(Convert.ToInt64(controllerContext.HttpContext.Request.Form("CurrentEvent.EventStatus.StatusID")))
        End If

        ' Do some other custom binding work here... 
        Return MyModel
    End Function
End Class

I set a breakpoint in both my controller action that handled the postback, and in EventBinder.BindModel.  What I found was that BindModel happened before my controller action was event invoked.  That explained why all of my attempts to capture this in the controller action failed (in particular, the object was always invalid by the time the controller action got ahold of it).  The binding was simply happening earlier in the call stack.  So, using the custom binder, I could translate ID to object.

And there was much rejoicing.

***

Fast forward a couple of weeks, and I found myself pressing my luck.  All of the validation that I had done up to this point was using the out-of-the-box "Required" attributes on the CorporateEvent business class properties.  For example:

 

Imports Castle.ActiveRecord
Imports System
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel

<ActiveRecord(Table:="CorporateEvent"), _
 Serializable()> _
Public Class CorporateEvent
    Inherits ActiveRecordBase(Of CorporateEvent)

    Private _ID As Long
    Private _Title As String
    Private _OccursOn As DateTime
    Private _EventStatus As Status


    <PrimaryKey("ID")> _
    Public Property ID() As Long
        Get
            Return Me._ID
        End Get
        Set(ByVal value As Long)
            Me._ID = value
        End Set
    End Property

    <[Property](), _
     Required(ErrorMessage:="Title is required."), _
     StringLength(200, ErrorMessage:="Maximum event title length is 200 characters.")> _
    Public Property Title() As String
        Get
            Return Me._Title
        End Get
        Set(ByVal value As String)
            Me._Title = value
        End Set
    End Property

    <[Property](), _
     Required(ErrorMessage:="Occurs On date/time is required.")> _
    Public Property OccursOn() As DateTime
        Get
            Return Me._OccursOn
        End Get
        Set(ByVal value As DateTime)
            Me._OccursOn = value
        End Set
    End Property


    <BelongsTo("StatusID"), _
     DisplayName("Status")> _
    Public Property EventStatus() As Status
        Get
            Return Me._EventStatus
        End Get
        Set(ByVal value As Status)
            Me._EventStatus = value
        End Set
    End Property
End Class

Now what I wanted to do was have specific fields in my model BECOME required when the Status was switched to Published.  In other words, I wanted "conditional validation".  I found a wonderful post and code sample from Simon Ince that does just that.  He creates a custom attribute called RequiredIf that makes the property required if another property has the named value.  For example:

 

Imports Castle.ActiveRecord
Imports System
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel
Imports ConditionalValidation.Validation

<ActiveRecord(Table:="CorporateEvent"), _
 Serializable()> _
Public Class CorporateEvent

    ...

    <[Property](), _
     RequiredIf("IsPublished", True, ErrorMessage:="Details Title is required for Published events."), _
     StringLength(100, ErrorMessage:="Maximum length is 100 characters.")> _
    Public Property TitleDetails() As String
        Get
            Return Me._TitleDetails
        End Get
        Set(ByVal value As String)
            Me._TitleDetails = value
        End Set
    End Property


    Public ReadOnly Property IsPublished() As Boolean
        Get
            If (IsNothing(Me.EventStatus)) Then Return False
            Return (Me.EventStatus.ID = Status.Statuses.Published)
        End Get
    End Property

End Class


So, I took his logic, and dropped it into a new project in my solution.  The property I wanted to evaluate was EventStatus.ID, but the attribute didn’t know how to evaluate "EventStatus.ID", so I created a boolean readonly property in CorporateEvent called "IsPublished" that did this evaluation for me.  I could now compare against True and False in the RequiredIf attribute.

Finally, I had to register the attribute in my Global.asax / Application_Start routine, like so:

 

DataAnnotationsModelValidatorProvider.RegisterAdapter(GetType(RequiredIfAttribute), GetType(RequiredIfValidator))


I wrote a unit test for one of the CorporateEvent properties (where EventStatus was Published, but my property wasn’t specified yet), and ran it.  Normally, the logic should have thrown it out with a validation error, but it did not.  Great!  My test failed!  I then wired up the RequiredIf attribute to that property, and re-ran the test.  It still failed.

Oh, duh.  I didn’t register the custom attribute in my test fixture – no wonder it wasn’t recognizing it.  I copied the same registration line I used in Application_Start to my TestFixtureSetUp method.  I ran the test again.  It still failed.

And so began two days of gesturing at the monitor.

I won’t bore you with all of my failed attempts and rabbit trails.  At the beginning of the second day, however, I realized that the only way I was going to get this thing to work is by reverting everything I had done up to that point, and starting over – this time dropping the pieces into the project in much smaller increments so I could see where it failed.  (Did I mention that I had dozens of tests breaking by the end of my initial pass at this, and I hadn’t the foggiest idea WHY they were breaking?!?)

By the time I had my breakthrough at the end of the second day, I was VERY close to scrapping the whole approach and doing the RequiredIf validation in the controller action.  I kept forcing myself forward, though, because I really wanted to do it right, and there didn’t seem to be any good reason why it shouldn’t have worked.

The breakthrough was a matter of timing.

In tracing through the logic, and inspecting the model each step of the way, I finally realized that my IsPublished property was never being set to True in time for the RequiredIf evaluation to happen.  As a result, IsPublished was always False, and so my custom validation was being skipped.  The reason it wasn’t being set in time was because EventStatus was being bound in EventBinder.BindModel, but the evaluation of RequiredIf was being done sometime before that point.  It was then that I started looking at what other functions I could override in EventBinder.  I overrode several that sounded like they might work, and put breakpoints in each to see the order that they get called and to inspect my model to see when things got bound.

What I found was that the CreateModel function got called first, and sets up the basic model object.  So, I tried moving the EventStatus logic from BindModel to CreateModel:

 

Public Class EventBinder
    Inherits DefaultModelBinder

    Protected Overrides Function CreateModel(ByVal controllerContext As System.Web.Mvc.ControllerContext, ByVal bindingContext As System.Web.Mvc.ModelBindingContext, ByVal modelType As System.Type) As Object
        Dim NewModel As Object

        ' Create the new model object from scratch
        NewModel = MyBase.CreateModel(controllerContext, bindingContext, modelType)
        NewModel.CurrentEvent = New CorporateEvent

        ' Set EventStatus here
        If (controllerContext.HttpContext.Request.Form.AllKeys.Contains("CurrentEvent.EventStatus.StatusID")) Then
            NewModel.CurrentEvent.EventStatus = Status.GetByID(Convert.ToInt64(controllerContext.HttpContext.Request.Form("CurrentEvent.EventStatus.StatusID")))
        End If

        Return NewModel
    End Function


    Public Overrides Function BindModel(ByVal controllerContext As System.Web.Mvc.ControllerContext, ByVal bindingContext As System.Web.Mvc.ModelBindingContext) As Object
        Dim MyModel As EventDetail

        MyModel = CType(MyBase.BindModel(controllerContext, bindingContext), EventDetail)

        ' Do some other custom binding work here...

        Return MyModel
    End Function
End Class

 

Now when it came time to evaluate my RequiredIf attribute, IsPublished had the right value, and could be properly evaluated.

And there was much rejoicing.

***

Despite the profound amount of grief this approach gave me (most of it being self-inflected), the RequiredIf attribute works very well and saves a lot of time when adding new fields to my model class.  I also learned quite a bit about how model binding and validation works in MVC, so looking back it really was worth the effort.

But man, what a quest.

Making sense on many levels – ASP.NET MVC 2 and Model-Level Error Reporting

In the previous episode of “Mark and the Chartreuse-Field Project”, Mark was working to get site-wide error reporting up and running.  Today, Mark tackles model-level error reporting.  Let’s tune in and see how he’s doing.

***

Early on in this project I learned how to associate a custom error message with a specific form field:

ViewData.ModelState.AddModelError("MyField", "My custom error message here")

That allowed the out-of-the-box validation messaging to highlight the offending field and display the message right next to it:

<%=Html.ValidationMessageFor(Function(model) model.MyField)%>

However, today I ran into a situation where I needed to be able to display a custom message that applied to the entire page, not a specific field.  There was a validation summary control at the top of the page already:

<%= Html.ValidationSummary(True) %>

My first thought was to associate the custom message with that control so it would appear there.  Following the pattern to associate a message with a specific field, my first attempt at code-roulette looked like this:

ViewData.ModelState.AddModelError("", "My custom error message here")

I then threw a dummy exception in the middle of one of my controller actions to force an error to appear.  No dice – the error was simply swallowed by the page.  I did some searching and came across a post (http://stackoverflow.com/questions/4017827/manually-adding-text-to-html-validationsummary) that mentioned using an asterisk as the field name to get the message to show up in the validation summary:

ViewData.ModelState.AddModelError("*", "My custom error message here")

Still no dice.  Ok, time to back up a minute.  Was my custom error even making it into the ModelState object?  I put a breakpoint on the line immediately after AddModelError, and inspected the ViewData.ModelState.Keys property:

(0): "id"
(1): "CurrentEvent.Title"

(17): "*"

So, if the error is getting added to the ModelState correctly, then why isn’t it showing up?  The answer came in two parts.  First, I was passing a “True” to Html.ValidationSummary.  This was configuring the control to not display messages that were tied to a field (the actual parameter name here is “excludePropertyErrors”).  My asterisk was being treated like another field name – one that didn’t match a field-level validation control – and was therefore being excluded by the validation summary control.  Second, a post from the ASP.NET forums (http://forums.asp.net/p/1628537/4193163.aspx) suggested that displaying model-level errors were not possible in MVC 2, and apparently were in MVC 3.  Upgrading wasn’t an option for me, so I needed to find another way.

What if I were to create a new model property called ErrorMessage, and then associate the custom messages with THAT field?  Then, I would just need to add a validation control for that field to my view.  I added the property, “ErrorMessage”, to my model and modified my AddModelError call like so:

ViewData.ModelState.AddModelError("ErrorMessage", "My custom error message here")

Then I added a Html.ValidationMessageFor control to my view, and tied it to this new property:

<%=Html.ValidationMessageFor(Function(model) model.ErrorMessage)%>

And voila!  Model-level errors in MVC 2!