Skip to content

Controller Conventions

Chris Martinez edited this page Sep 18, 2020 · 3 revisions

There are a few implicit conventions to be aware of.

Every Controller Has an API Version

Once you opt into API versioning, every API controller has an API version. This is true even if the controller does not have an explicit attribute or configured convention. When otherwise unspecified, the version applied to a controller derives from ApiVersioningOptions.DefaultApiVersion.

Naming

ASP.NET provides a built-in convention for controller names that use the form [name]Controller where Controller will be trimmed off when exactly that text. API Versioning slightly expands this convention. It will honor the convention of [name][#]Controller. This allows you to have two controller types in the same namespace for different API versions, but for the same resource; for example, ValuesController and Values2Controller will both have the name Values. Naming is important for grouping controllers together.

Alternate grouping, such as by route template, is unreliable. For example, the template api/values/{id} and api/values/{id:int} are semantically equivalent. Understanding that two URLs are not identical, but are semantically equivalent can be complex and requires template parsing to understand them.

.NET only allows a single, unique type name per namespace. You can use the same name by splitting your controllers into different namespaces. You can also explicitly define the controller name using the ControllerNameAttribute (ex: [ControllerName("Values")]).

API Controllers

Applies to ASP.NET Core only

A controller is just a controller in ASP.NET Core; there is no distinction between a UI Controller and an API Controller. Some applications mix UI controllers and API controllers together. This will result in all controllers requiring an API version, which is undesirable for UI controllers. The advent of the ApiControllerAttribute made it possible to disambiguate the two types of controllers.

API Versioning 3.0 introduced two new interfaces:

interface IApiControllerFilter
{
    IList<ControllerModel> Apply( IList<ControllerModel> controllers );
}

interface IApiControllerSpecification
{
    bool IsSatisifedBy( ControllerModel controller );
}

The IApiControllerFilter filters which controllers should be considered API controllers. The default implementation typically does not need to be replaced.

The IApiControllerSpecification defines a specification as to whether a particular controller is an API controller.

There are two built-in specifications:

  • ApiBehaviorSpecification - matches controllers decorated by [ApiController]
  • ODataControllerSpecification - matches controllers decorated by [ODataRouting]

An API controller will be considered any controller that matches at least one specification. If a built-in specification does not meet your specific needs, you can create your own:

// considers controllers inheriting from Controller to be a UI controller
public class NonUIControllerSpecification : IApiControllerSpecification
{
    readonly Type UIControllerType = typeof( Controller ).GetTypeInfo();

    public bool IsSatisfiedBy( ControllerModel controller ) =>
        !UIControllerType.IsAssignableFrom( controller.ControllerType )
}

Register your specification in the services configuration:

services.TryAddEnumerable(
    ServiceDescriptor.Transient<IApiControllerSpecification,
                                NonUIControllerSpecification>() );

To disable this feature, change ApiVersioningOptions.UseApiBehavior to false.

Clone this wiki locally