ASP.NET 5: Using POCO controllers
ASP.NET 5 supports POCO controllers. Yes, controller classes that doesn’t extend Controller base class. These controllers look a little bit different by some small aspects and sometimes we may need to help framework detect our POCO controllers. This posting gives you complete overview of POCO controllers in next ASP.NET.
Simple POCO controller
First let’s define simple controller that doesn’t inherit from Controller base class.
public class PocoController
{
public IActionResult Index()
{
return new ContentResult() { Content = “Hello from POCO controller!” };
}
}
To make this controller work with views we need some additional code.
public class PocoController
{
private readonly IModelMetadataProvider _metadataProvider;
public PocoController(IModelMetadataProvider metadataProvider)
{
_metadataProvider = metadataProvider;
}
public IActionResult Index()
{
var viewData = new ViewDataDictionary<string>(_metadataProvider);
viewData.Model = “Hello from POCO controller!”;
return new ViewResult() { ViewData = viewData };
}
}
Now we have basic primitive POCO controller that ASP.NET 5 will happily use.
Fooling ASP.NET
Now let’s do the little trick and call our controller just as Poco.
public class Poco
{
// …
}
When trying to use controller now we run to problems.
Why? Because ASP.NET cannot detect controller anymore.
How controller is detected by default?
When sniffing around in MVC source you can find the method that MVC uses to find out if given type is controller type of not. It is done in DefaultActionDiscoveryConventions class.
public virtual bool IsController([NotNull] TypeInfo typeInfo)
{
if (!typeInfo.IsClass ||
typeInfo.IsAbstract ||
typeInfo.ContainsGenericParameters)
{
return false;
}
if (typeInfo.Name.Equals(“Controller”, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return typeInfo.Name.EndsWith(“Controller”, StringComparison.OrdinalIgnoreCase) ||
typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo);
}
If we have POCO controller and we don’t name it as SomethingController then MVC is not considering our POCO controller as controller.
Using action discovery convention
If there are additional rules for detecting controllers we can use action discovery conventions to tell MVC if class is constructor or not. Here is simple discovery convention class that works with our POCO controller.
public class MyActionDiscoveryConventions : DefaultActionDiscoveryConventions
{
public override bool IsController(TypeInfo typeInfo)
{
var isController = base.IsController(typeInfo);
return isController || typeInfo.Name.Equals(“Poco”, StringComparison.OrdinalIgnoreCase);
}
}
To make MVC use this class we have to register it with built-in dependency injection system, We will do it in ConfigureServices() method of our Startup class by calling AddTransient() method.
public void ConfigureServices(IServiceCollection services)
{
// …
services.AddMvc();
services.AddTransient<IActionDiscoveryConventions, MyActionDiscoveryConventions>();
}
If we run our application now and try to use POCO controller it works again.
Defining ControllerAttribute
If you are building some extensible system and you need some better way how to detect controllers then besides naming controllers appropriately you can also define attribute in some API library that is available for developers.
public class ControllerAttribute : Attribute
{
}
Other developers who are building plugins for your system can decorate their controllers with this attribute.
[Controller]
public class Poco
{
// …
}
And here is how you can detect controller in action discovery convention.
public class MyActionDiscoveryConventions : DefaultActionDiscoveryConventions
{
public override bool IsController(TypeInfo typeInfo)
{
var isController = base.IsController(typeInfo);
return isController || HasControllerAttribute(typeInfo);
}
private bool HasControllerAttribute(TypeInfo typeInfo)
{
return typeInfo.GetCustomAttribute<ControllerAttribute>(true) != null;
}
}
You can also use marker interface for POCO controllers or some interface with properties and methods defined if your POCO controllers have to provide some functionalities.
Wrapping up
Creating POCO controllers is simple. To use views and other goodies provided by controllers base class we have to use dependency injection to get required services to our POCO controller. If we don’t use MVC regular naming style for controllers we have to write action discovery convention and register it with built-in dependency injection service. To provide common strategy to detect POCO controllers with arbitrary names we can use special attribute or interface for this. We still can inherit our POCO controllers from classes we want.
Clean article Gunnar. I do have to bring into question, what reasons there would be for separating the convention Controller naming to a POCO.
I understand that this is another separation and more custom capability to configure your asp.net applicaiton. But at first glance I see two glaring issues:
– Readability and clarity of intent
– Configuration is just moved to another area (vs. current naming and inheritance configuration)
I am just curious of your thought on what scenario’s this would end up providing a benefit? Maybe grouping controllers by different names to specify intent?
Thanks.
While this is great, I was surprised not to see MEF under the covers. Since MEF basically scours my assemblies, why not use that in a discover process? In the end it could afford a great deal of flexability.
Max, Corey Kaylor here mentions better testability when controllers have no dependencies to framework (Dependency Free POCO Controller With ASP.NET vNext). It’s easier to inject fakes and there are no invisible dependencies between properties and methods defined by base class.
Dave, I think ASP.NET team is trying to keep things as minimal as possible. There are simple rules how controllers are detected and if you have something else in your mind then you can easily implement and inject your own rules. If you like MEF then, of course, you can come out with action discovery that uses MEF.
It is probably worth mentioning that only dependencies which reference one of the following namespaces will be searched for controllers:
Microsoft.AspNet.Mvc
Microsoft.AspNet.Mvc.Abstractions
Microsoft.AspNet.Mvc.ApiExplorer
Microsoft.AspNet.Mvc.Core
Microsoft.AspNet.Mvc.Razor
Microsoft.AspNet.Mvc.Razor.Host
Microsoft.AspNet.Mvc.TagHelpers
Microsoft.AspNet.Mvc.Xml
Microsoft.AspNet.PageExecutionInstrumentation.Interfaces