ASP.NET
ASP.NET MVC REST API WINDSOR CONTROLLER FACTORY
Mar 11th
I’m using the ASP.NET MVC REST Toolkit for a REST API I’m building for a mobile infrastructure. Long story short, it will be responsible for service hundreds of thousands (if not millions) of users through their mobile phones and rich applications (Android, iPhone and eventually the WP7 Phones).
The one thing that the Toolkit did not provide out of the box was a way to use a container in the controller factory. Therefore I altered the ResourceControllerFactory to use Windsor Container as its default container. The code works great and I’m able to utilize dependency injection all throughout my app now.
The code for this is below:
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using Castle.MicroKernel;
namespace System.Web.Mvc.Resources
{
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Mime;
using System.Text;
using System.Web.Routing;
/// <summary>
/// Specialized ControllerFactory that augments the base controller factory to make it RESTful - specifically, adding
/// support for multiple formats, HTTP method based dispatch to controller methods and HTTP error handling
/// </summary>
public class ResourceControllerFactory : IControllerFactory
{
// Note: This has been changed from the regular controller factory provided
// by the ASP.NET Team so that we can use Windsor Container to resolve
// container dependencies.
readonly IKernel _kernel;
const string restActionToken = "$REST$";
public ResourceControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
IController ic = _kernel.Resolve<IController>(controllerName.ToLowerInvariant() + "controller");
Controller c = ic as Controller;
if (c != null && WebApiEnabledAttribute.IsDefined(c))
{
IActionInvoker iai = c.ActionInvoker;
ControllerActionInvoker cai = iai as ControllerActionInvoker;
if (cai != null)
{
c.ActionInvoker = new ResourceControllerActionInvoker();
string actionName = requestContext.RouteData.Values["action"] as string;
if (string.IsNullOrEmpty(actionName))
{
// set it to a well known dummy value to avoid not having an action as that would prevent the fixup
// code in ResourceControllerActionInvoker, which is based on ActionDescriptor, from running
requestContext.RouteData.Values["action"] = restActionToken;
}
}
}
return ic;
}
public void ReleaseController(IController controller)
{
_kernel.ReleaseComponent(controller);
}
// This ActionInvoker allows us to dispatch to a controller when no action was provided by the routing
// infrastructure, but the information is available in the request's HTTP verb (GET/PUT/POST/DELETE)
class ResourceControllerActionInvoker : ControllerActionInvoker
{
public ResourceControllerActionInvoker()
{
}
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
if (actionName == restActionToken)
{
// cleanup the restActionToken we set earlier
controllerContext.RequestContext.RouteData.Values["action"] = null;
List<ActionDescriptor> matches = new List<ActionDescriptor>();
foreach (ActionDescriptor ad in controllerDescriptor.GetCanonicalActions())
{
object[] acceptVerbs = ad.GetCustomAttributes(typeof(AcceptVerbsAttribute), false);
if (acceptVerbs.Length > 0)
{
foreach (object o in acceptVerbs)
{
AcceptVerbsAttribute ava = o as AcceptVerbsAttribute;
if (ava != null)
{
if (ava.Verbs.Contains(controllerContext.RequestContext.GetHttpMethod().ToUpperInvariant()))
{
matches.Add(ad);
}
}
}
}
}
switch (matches.Count)
{
case 0:
break;
case 1:
ActionDescriptor ad = matches[0];
actionName = ad.ActionName;
controllerContext.RequestContext.RouteData.Values["action"] = actionName;
return ad;
default:
StringBuilder matchesString = new StringBuilder(matches[0].ActionName);
for (int index = 1; index < matches.Count; index++)
{
matchesString.Append(", ");
matchesString.Append(matches[index].ActionName);
}
return new ResourceErrorActionDescriptor(controllerDescriptor, HttpStatusCode.Conflict, string.Format(CultureInfo.CurrentCulture, "Error dispatching on controller {0}, conflicting actions matched: (1)", controllerDescriptor.ControllerName, matchesString.ToString()));
}
}
return base.FindAction(controllerContext, controllerDescriptor, actionName) ??
new ResourceErrorActionDescriptor(controllerDescriptor, HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, "Error dispatching on controller {0}, no actions matched", controllerDescriptor.ControllerName));
}
// This class is used when we don't find an ActionDescriptor or find multiple matches
// in this case we want to return an error response but throwing an HttpException from
// FindAction bypasses the InvokeExceptionFilters, so instead we throw in this custom ActionDescriptor
class ResourceErrorActionDescriptor : ActionDescriptor
{
ControllerDescriptor controllerDescriptor;
string message;
HttpStatusCode statusCode;
public ResourceErrorActionDescriptor(ControllerDescriptor controllerDescriptor, HttpStatusCode statusCode, string message)
{
this.message = message;
this.statusCode = statusCode;
this.controllerDescriptor = controllerDescriptor;
}
public override string ActionName
{
get { return restActionToken; }
}
public override ControllerDescriptor ControllerDescriptor
{
get { return this.controllerDescriptor; }
}
public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
{
HttpException he = new HttpException((int)this.statusCode, this.message);
ResourceErrorActionResult rear;
if (!WebApiEnabledAttribute.TryGetErrorResult2(controllerContext.RequestContext, he, out rear))
{
rear = new ResourceErrorActionResult(new HttpException((int)this.statusCode, this.message), new ContentType("text/plain"));
}
return rear;
}
public override ParameterDescriptor[] GetParameters()
{
return new ParameterDescriptor[0];
}
}
}
}
}
You can easily replace the Windsor stuff with StructureMap, NInject, Autofac, etc.
Enjoy.
Editable Grid / List Binding in MVC2
Feb 27th
Steve Sanderson has a great post on how to perform list binding with MVC2 and custom HTML Prefix Scoping. However, it did not demo the out of box functionality that MVC2 provides, so I’m going to do that here. Please note, I’m using MVC2 RC2 in this example. At the end if this post you will be able to download the solution and run/debut it on your machine.
The App
I’m going to be using Steves sample app. When you first first fire it up you will be presented with a link to go to Donn’s list binding demo. This demo emulates using the built in features of MVC2 to great an editable grid or list. It will look like this:
After clicking on that link you will be presented with a grid / list that is editable. I’m going to use the EditorFor() expression based input builder and some custom editor for templates to layout the grid. In the end, the grid will look like this (yes, not pretty, but it demonstrates what can be done with some of the editor templates.
This is a order list that is set up in the controller manually. The Controller invokes the index action with a customer view model. The customer object has a list of orders, and each order has a list of line items. The objects look like this:
Customer
Order
LineItem
Solution Layout
The solution is lain out in a way that we can render the Index.aspx, which will render an editor for the order. The OrderRow.ascx file is the file we use to display the editor. We do that in the Index.aspx page like so:
These editor templates are stored in your controllers view folder, under a folder called “EditorTemplates” as shown below:
The OrderRow.ascx Editor will handle how each editor is formatted for editing:
We will be using another editor template, “LineItemRow” to format how each line item should be edited. We are using one editor to create another editor, cool!
Here’s the LineItemRow.ascx (which is called from above via the “LineItemRow” parameter in the EditorFor expression based call:
Incremental Sequencing
The reason the list binding works is because we’re using incremental sequencing while building our editor. We’re using for loops. This allows MVC to know where it’s at during the creation of the form. What does this mean in plain English? MVC will generate incremental name and id’s in the HTML, which, when posting back, the DefaultModelBinder will be able to bind back into the complex object type.
Lets delve into this a bit further. This screen is generated with incremental id’s, and the id’s are shown below in the screen shot for ease of understanding:
The raw HTML looks like this (click to enlarge):
Using the incremental sequencing and editor templates allows us to change the values and post them back to the form.
Example
Lets change the name of “Sneakers” to “Shoes”. Then we’ll post the form. Here’s what we see in the post (via the debugger):
Conclusion
Now you can edit the list, post the new updated results and persist them. Fairly simple.
Download the Sample Project (VS2010)
If you’re using VS2008, you can copy the files into a VS2008 MVC2 RC2 Project (minus the project) and it should work.
ASPNET MVC2: TempData Now Persists
Feb 26th
In MVC1, TempData did not persist:
The value of TempData persists only from one request to the next. (source)
This has changed in MVC2. The TempData dictionary will not persist data until the key is read from the dictionary.
…We’ve changed the implementation in MVC 2 slightly as a result: the value will be removed from TempData after the request in which it is read, so it will continue to exist in your TempData dictionary until you display it in some page. This allows a multi-redirect scenario (such as Windows Live ID login) to use TempData and have it still be hanging around until you’re ready for it. (source)
This is very important to note because if you use MVCContrib’s ModelStateToTempData attribute the values in the modelstate will be persisted across requests as it will be present in the TempData across requests. If you’re using button names as decision criteria in your Http Post then you might notice that your ModelState is traversing into other actions and showing invalid validation on pages where the model is not of the given type of the model state info that is being presented.
New DimeCast: Spark Partials Part 2
Jan 28th
I have another DimeCast up on DimeCasts.net.
More Info: In this episode we are going to continue looking at how to use Partials within the Spark View Engine.
In this episode we will take a deeper look at partials and learn more about their _undocumented_ features. We will also be learning how partials will allow us to create clean, compartmentalized and well organized code.
Check it out here.
MVC vs. WebForms. Stop It.
Jan 22nd
Since Microsoft joined the MVC foray with ASP.NET MVC (other options existed previously, such as Monorail) I’ve personally witnessed (and been part of ) a lot of MVC vs. WebForms discussions. Usually these discussions end up with opposite sides taking their ball and going home in a pissed-off offended rage. Usually the WebForm fans snicker and say “Why change whats broken, I have X number of users and I make X money off of my app which has X % of uptime and it work great!”, etc etc etc. They’re right. Why change it? The MVC folks usually reply with “What about standards, ease of use, integration, ease of use with js frameworks, restful architectures, separation of concerns, SOLID, maintainability, and testability as well as a working app”? They’re also right. What about those things the MVC fans talk about? Most of those things require you jump through some hoops to do them in WebForms. Some of these problems were solved long ago by some smart WebForms people. Thats the counter argument. But then again all programming problems have a common counter argument – Hasnt everything already been solved by Lisp and Smalltalk (or so the saying goes)?
Each side of this arguments has their own opinion and their own valid points. So be it. Learn to live with it.
Personally, I’m a HUGE ASP.NET MVC advocate. However I’m realistic in my thought process and I use the right tool for the right job.
Here’s a perfect example: This last weekend I helped lead the Twin Cities GiveCamp team to a successful web project implementation for the Animal Humane Society. The solution was a web application. Almost everyone I know would say “Oh cool! You did a nice fancy MVC App right?!” Nope. Sure didnt. Myself and the team agreed that while MVC was what WE as a group wanted to do, it wasnt the right thing for the job.
We had 48 hours to create a solution for the non-profit. We needed simple app that was forms over data and we also needed to make sure that anyone who maintains this code base in the future would be able to update it. With not everyone knowing asp.net mvc we chose WebForms. Everyone on the team knew WebForms and how it worked. If we went with MVC I think the solution might have have been cleaner, but I do feel we would have NOT completed the job on time. In the end the MVC learning curve was too much for the team. Therefore in this instance, MVC was not the correct choice. We ended up using ASP.NET 3.5 WebForms, jQuery, and some other controls to get the job done. It worked out great and we’re in the process of setting the client up with a hosting account with DiscountASP.NET so they can start using the app as soon as possilbe. Choosing web forms allowed us to get the app done save their organizatoin time and money which allows them to improve treatment and facilities for the animals (which is the real end goal). If we chose MVC we most likely would not have finished and the entire event would have been a failure. No business problem solved, plain and simple.
The proof is in the pudding: Use what works best for the given problem … and really … who cares what technology you use to implement a given solution. If it doesnt work, you failed. If it works, you succeeded. This holds true if you’re using ASP.NET vs. Rails vs. PHP vs. JSP. In the end, ship working software. Otherwise, you’ve taken a long ride on the dreaded fail train.
What I’m trying to say is … do what you need to do to get the job done. Don’t be a technical-framework-nazi. If you love ASP.NET MVC, cool! If you love WebForms, cool! Get the job done, get paid, pay your bills and go home and spend time with your family.
What’s a big deal to you today, won’t be a year from now. Remember, no one ever laid down in their deathbed thinking “Ya know what, I should have used ASP.NET MVC for that one project.”