Article Index

Sample Opinionated Controller

In my post on testing opinionated controllers, I mentioned how our controller actions follow a strict convention: one view model object in, one view model object out, and leave the ViewResult creation to the ControllerActionInvoker. Jimmy Bogard asked me to post the controller base class that makes this work. The implementation that we use is too involved to post as a sample, but I was able to create a "pseudo-code" version that demonstrates the ideas. I call it pseudo-code because it is just enough to demonstrate the concept, but it is nowhere near production code. It doesn't account for any edge cases, or really any other case than the single one I tested.

To be clear, neither this, nor the code in my previous post, is straight out of our source tree. It is more "inspired by" as opposed to an accurate representation of our actual code.

The code below shows:

public class OpinionatedController : Controller { public OpinionatedController() { ActionInvoker = new OpionatedActionInvoker(); } public class OpionatedActionInvoker : ControllerActionInvoker { protected override object GetParameterValue(ParameterInfo parameterInfo) { var parameterType = parameterInfo.ParameterType; if (parameterType.IsPrimitive) { return getValueFromInput(parameterInfo.Name, parameterType); } var complexActionParameter = Activator.CreateInstance(parameterType); foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(complexActionParameter)) { object propertyValue = getValueFromInput(descriptor.Name, descriptor.PropertyType); if (propertyValue != null) { descriptor.SetValue(complexActionParameter, propertyValue); } } return complexActionParameter; } private object getValueFromInput(string parameterName, Type parameterType) { object propertyValue; ControllerContext.RouteData.Values.TryGetValue(parameterName, out propertyValue); if (propertyValue == null) { propertyValue = ControllerContext.HttpContext.Request.Params[parameterName]; } return TypeDescriptor.GetConverter(parameterType).ConvertFrom(propertyValue); } protected override ActionResult InvokeActionMethod(MethodInfo methodInfo, System.Collections.Generic.IDictionary<string, object> parameters) { object[] actionMethodParams = new object[0]; if (methodInfo.GetParameters().Length > 0) { actionMethodParams = new[] { parameters.Values.ElementAtOrDefault(0) }; } var viewModel = methodInfo.Invoke(ControllerContext.Controller, actionMethodParams); var responseViewModel = viewModel as ResponseViewModel; if (responseViewModel != null && responseViewModel.OverrideResult != null) { return responseViewModel.OverrideResult; } return new ViewResult { ViewData = new ViewDataDictionary { Model = viewModel } }; } } } public class ResponseViewModel { public ActionResult OverrideResult { get; set; } }

I created a solution that I used for testing all of this, to prove it works. It is the adaptation of the codecampserver SpeakerController that I used for my previous post. There is probably a lot of cruft and aborted experiments in there that you can safely ignore. To see it all work, run the website and go the url: /austincodecamp/speakers?displaypage=2&displaypagecount=15 (substituting different values for the code camp name and display page stuff to prove it isn't hardcoded). Download the solution (built with VS2008 and ASP.NET MVC Preview 5)