ASP.NET Core MVC Application Model – Part II

EDIT 03/08/2016: this post was originally written for ASP.NET MVC 6 beta versions. While most of the post and its general idea still applies to ASP:NET Core MVC, some details may not be valid anymore and links might be broken.


On the previous post of this series I introduced the ApplicationModel, its main types and how it can be used to customize the final action descriptors. On this post I’ll go into a couple code samples to make everything clearer.

On previous versions of Web API, one could prefix action methods with the HTTP method name to make them apply to that method (e.g. GetNNNN, PostNNNN). This convention is not present on MVC Core, but the behavior is easy to mimic using an application model convention.

public class HttpMethodActionPrefixConvention : IApplicationModelConvention
{
    public void Apply(ApplicationModel application)
    {
        var actions = application.Controllers.SelectMany(c => c.Actions);
        foreach (var action in actions)
        {
            if (action.ActionName.StartsWith("Get"))
            {
                action.HttpMethods.Add("GET");
                action.ActionName = action.ActionName.Substring(3);
            }
            // …
        }
    }
}

As I mentioned on the previous post, a convention is a type that implements IApplicationModelConvention. In the example above, I’m iterating all the actions that were collected into the application model and checking if their names start with an HTTP method. If so, that HTTP method is added to the ActionModel HTTP methods and the final action name is adjusted. This way, if we have a GetData action it will be constrained to HTTP GET and the action name throughout the application (e.g. route matching) will be Data.

Finally, the convention is added to MVC options on the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore(options =>
    {
        options.Conventions.Add(new HttpMethodActionPrefixConvention());
    });
}

Besides conventions, one might also use the different types that are considered by the framework when building the application model (e.g. IActionConstraintMetadata, IFilterMetadata). For this post I’ll stick with IActionConstraintMetadata which is a means of determining if an action is suitable for a given HTTP request.

Recalling the previous post, the default application model provider searches whether attributes applied to action methods implement specific types, namely IActionConstraintMetadata. However, this is a marker interface, meaning there must be something more to it. Looking into ActionDescriptor we can see that it still has a collection of IActionConstraintMetadata. If we keep going up we’ll end-up on action selection. At this point, the IActionConstraintMetadatas are processed to obtain IActionConstraints, which actually determine if an action is suitable for a request, as illustrated below. The context provides access to things such as the HttpContext, the candidate ActionDescriptors for the current request and the ActionDescriptor being evaluated.

public interface IActionConstraint : IActionConstraintMetadata
{
   int Order { get; }
   bool Accept(ActionConstraintContext context);
}

Taking another look at the DefaultActionSelector one can see that IActionConstraintMetadatas are processed by – guess what? – another set of providers. As with other providers so far, there’s a default implementation that should suite all our needs. Here an excerpt from its source code:

var constraint = item.Metadata as IActionConstraint;
if (constraint != null)
{
    item.Constraint = constraint;
    return;
}

var factory = item.Metadata as IActionConstraintFactory;
if (factory != null)
{
    item.Constraint = factory.CreateInstance(services);
    return;
}

This means that, by default, for a given IActionConstraintMetadata there are two options:

  1. It is an IActionConstraint – The instance can be used directly. This is good enough for most action constraints, since the Accept method gets contextual information.
  2. It is an IActionConstraintFactory – The factory is invoked passing the services locator. Note that a IActionConstraintMetadata is typically a custom attribute, which means it has limited flexibility on construction. If the constraint you’re implementing depends on external services, you can use the factory approach to create it.

At this point, all that’s left is an example. Lets say we want to have an API endpoint that accepts POST requests with two different media types. Instead of if/else on a single action method, we can add an action constraint based on the request’s content type. Here’s how I’d like to write my actions:

[HttpPost, MediaType("application/json")]
public void MyAction(MyModel model)
{
}

[HttpPost, MediaType("image/png")]
public void MyAction(Stream content)
{
}

Implementing MediaTypeAttribute  is pretty straight forward: the framework already includes a base class for attributes that are action constraints, which we can use in this case:

public class MediaTypeAttribute : ActionMethodSelectorAttribute
{
    private readonly string mediaType;

    public MediaTypeAttribute(string mediaType)
    {
        this.mediaType = mediaType;
    }

    public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
    {
        return routeContext.HttpContext.Request.ContentType == this.mediaType;
    }
}

And that’s it! On these two posts I covered a lot about application model, how it’s built and how we can use it to customize the final action descriptors on our MVC 6 applications. Hope this helps!

Advertisement

ASP.NET Core MVC Application Model – Part I

EDIT 03/08/2016: this post was updated to reflect naming changes from MVC 6 to ASP.NET Core MVC. All the contents still apply.


On my previous posts about routing on ASP.NET 5 I mentioned how action descriptors are used to build routes for attribute routing. Action descriptors represent the HTTP endpoints on the application and, as expected, they are also used when selecting an action for a given request.

The collection of action descriptors that are used by the framework is obtained via a set of IActionDescriptorProvider. While it is possible to add/replace IActionDescriptorProviders, the built-in implementation includes another mechanism to enable customization of action descriptors on most scenarios: the ApplicationModel.

The application model is a representation of the application components discovered by the framework, namely controllers and their actions. As illustrated below, this model includes a lot of metadata on the different components.

application_model

The application model is also created by a set of providers, represented by the IApplicationModelProvider interface. Again, it’s not very likely that you need to replace/extend these providers, since the application model is made available for customization (more on this soon). It’s also worth pointing that, in order to build the application model, the framework needs to find the controller types available on the application. The lookup is based on IControllerTypeProvider and can also be configured as needed. This post describes controller discovery in more detail.

Back to the application model diagram, lets go into some of the types and how they are used by the default application model provider:

  • ControllerModel – represents a controller that was discovered by the application model provider. An instance of this class is created for each controller that is discovered by the aforementioned IControllerTypeProvider. This is also when the controller names are defined.
  • ActionModel – represents an action of a controller. An instance of this class is created for each eligible action on a controller. There are multiple requirements for a method to become an action, such as being public, non-abstract and not inherited from object.
  • IFilterMetadata – marker interface to signal a filter. Filters can be applied at application (global), controller or action level. The default application model provider searches for filters metadata on the custom attributes of controller and action types. The final set of filters on each action is assembled later on, when building the action descriptors.
  • IRouteConstraintProvider – also specified via custom attributes, this interface can be used to specify constraints for a given route value (e.g. area=xpto).
  • IActionConstraintMetadata – marker interface for action constraints, i.e. types that will help determine if an action is suitable for a given HTTP request. I’ll exemplify their usage on the second post of this series.

I previously mentioned that the application model is a means of customizing action descriptors, but that’s not completely accurate. In fact, that customization is supported by the default IActionDescriptorProvider using application model conventions, which are applied to the application model:

internal protected IEnumerable<ControllerActionDescriptor> GetDescriptors()
{
    var applicationModel = BuildModel();
    ApplicationModelConventions.ApplyConventions(applicationModel, _conventions);
    return ControllerActionDescriptorBuilder.Build(applicationModel);
}

As shown on the snippet above, a set of conventions is applied to the application model before it is used to build the final action descriptors. A convention is represented by the IApplicationModelConvention interface and has access to the whole application model for modification:

public interface IApplicationModelConvention
{
  void Apply(ApplicationModel application);
}

One might ask why the application model was introduced, instead of having the developers directly customize action descriptors. Despite not being sure about the reasons, I’d say the ASP.NET team wanted to provide a customization model that is closer to how a developer organizes the application (e.g. controllers that contain actions). The final action descriptors are more complex and there’s a lot of plumbing to be done to create them that probably shouldn’t surface to the developer. For instance, a single ActionModel might result on multiple ActionDescriptors, depending on attribute routing. The application model is simpler and easier to modify, leaving all the final hard work to the framework.

On the following post I’ll bring up some code exemplifying the usage of conventions and some other interfaces that are present on the application model. Stay tuned!