.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.
My CODE Magazine Article Is Live
Feb 25th
My first article with CODE Magazine is live. You can read it here. I cover ASP.NET MVC with the Spark View Engine. I cover a wide range of things and go in depth with examples with topics that include:
- Inline Code
- Element Constructs
- Partials
- Layouts
- and more …
I hope you enjoy it! I enjoyed writing it.
A big thanks goes out to Chris G. Williams (twitter)for doing a stellar job on the tech editing.
Read it Here
LINQ: ToLookup vs ToDictionary
Feb 12th
Some people are not sure of the difference between ToLookup vs ToDictionary LINQ methods.
To break it down real simple:
- One is a look up (ToLookup)
- One is a dictionary (ToDictionary)
Duh. That’s what you’re probably thinking. The real difference is understanding what each of these data structures do.
Lookups
A lookup is a key value pair in which multiple keys of the same value can exist, therefore resulting in a “lookup” type of scenario. The perfect example for this is to assume that we have a List of Author objects. The author object contains the following:
- First Name
- Last Name
- Year their first book was published
- Social Security Number
Assume that we’d like to give the user the option to select the authors based upon the year they published their first book in a WPF app. We could do that like this:
// Returns a list of Authors var authors = authorService.FindAll(); // Turn the list into a lookup for later use var authorLookup = authors.ToLookup(k => k.YearFirstPublished, v => v); // Further down in the code // Find all authors who published their first book in 1969 // Returns an enumerable of Authors that match that lookup value var authorsPublishedInParticularYear = authorLookup[1969];
This will return all authors who published their first book in 1969. We could use this look up later in the application as well to look up other values.
The key thing to note here is that a Lookup can have multiple keys of the same value.
Dictionaries
A dictionary is a key value pair in which a key can only exist once within the dictionary. You cannot have two entries with the same key. This is the difference between the Dictionary and Lookup data structures.
Using the same Author object we described above we can find an author by their Social Security Number if we turned the list into a dictionary for fast lookups based upon a unique key (the social security number).
This will return a single Author object who’s social security number matches ‘555-55-5555’.
// Returns a list of Authors var authors = authorService.FindAll(); // Turn the list into a lookup for later use var authorDictionary = authors.ToDictionay(k => k.SocialSecurityNumber, v => v); // Further down in the code // Finds the author with 555-55-5555 as their Social Security Number var author = authorDictionary["555-55-5555"];
Conclusion
While dictionaries and lookups seem to be nearly the same data structure, they provide completely different functionality based upon the value the key possesses.
Suspending Resharper 5
Dec 4th
For those of you who don’t know, ReSharper is no longer a Visual Studio add-in. Previously if you needed to disable ReSharper for whatever reason you could do so in the Add-In Manager in Visual Studio. You no longer can. Disabling ReSharper is now termed “Suspending” it. You can suspend ReSharper from the Tools > Options > Resharper options window. Press Suspend. ReSharper will now be suspended.
I know, why would I disable ReSharper? Well, at my current client I’m working on a VB.NET ASP.NET Web Forms application which talks to Microsoft CRM through the CRM WebService and LinqToCrm. The web service proxy that gets generated is over 94,000 lines long. Thats 94,000 lines of VB.NET. When ReSharper starts to analyze that file (as well as the other VB.NET files in the app) all hell breaks loose. The IDE locks up and I can’t do a single thing. Therefore I had to suspend ReSharper while working in this project.
Here’s the crux … the only VB.NET project is the Website, all other libraries are C# (thankfully) and I want to use ReSharper. Going through the tools menu each time I need to Suspend/Resume Resharper is a royal PITA. Thankfully ReSharper gave us a key command: Resharper_ToggleSuspend to assist in toggling the suspension of ReSharper. This is not mapped to any key combo by default. What I have done is mapped it to CTRL + ALT + F6. Now when I need to turn ReSharper On/Off when I enter/leave the VB.NET project I hit the key combo and I’m back in action (or out of action in regards to turning it off). Super easy. Booyakasha.
Sending Email Through .NET Blog Engine on RackSpace Cloud
Nov 4th
I’m a cloud customer and I really really really love it. I can set up sites, servers, etc SUPER quick. It’s almost like owning my own server without all of the admin junk to take care of. However, sometimes I run into some issues with configuration, such as I did recently with Rackspace Cloud.
I migrated my blog to the cloud and just recently a user sent me an email via my contact form. Unfortunately I never got it (he notified me via Twitter that he sent me a message).Therefore, my contact form has been down for over a month. I have NO idea how many emails have been lost in the ether. If you’ve sent me an email over the last month, via my contact form, I apologize – I never received it. Please send it to me again!
SOLUTION
In order to get email sending from the engine again I updated my settings in Blog Engine. I set up the SMTP server and sent a test email. FAIL. Nope, didnt work. After talking to the support team I was informed that you have to provide the following to get your site to send email via SMTP (all of these are set in Blog Engines settings):
- A valid email user as the “authenticated user” to send email from. This would be something like username@yourdomain.com
- The users password
- SMTP server mail.[yourserver].com
- Your mail port: 25
I was not aware of this as it is not anywhere on the help for Rackspace cloud. I was told they will update their docs to reflect the instructions on how to send email. Hopefully this will help someone who uses the cloud for their Blog Engine if they run into the same problem.