-
Notifications
You must be signed in to change notification settings - Fork 704
Controller Conventions
There are a few implicit conventions to be aware of.
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.
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.
Unfortunately, this can cause an issue for service API versioning if you want to split the implementation across different types. If the defining type is in a different .NET namespace, then there is no issue; however, if they are in the same namespace there would be a name collision. For example:
namespace My.Services.V1
{
[ApiVersion( 1.0 )]
[Route( "[controller]" )]
public class HelloWorldController : ControllerBase
{
[HttpGet]
public string Get() => "Hello world v1.0!";
}
}
namespace My.Services.V2
{
[ApiVersion( 2.0 )]
[Route( "[controller]" )]
public class HelloWorldController : ControllerBase
{
[HttpGet]
public string Get() => "Hello world v2.0!";
}
}
Controllers separated by .NET namespace
namespace My.Services.Controllers
{
[ApiVersion( 1.0 )]
[Route( "[controller]" )]
public class HelloWorldController : ControllerBase
{
[HttpGet]
public string Get() => "Hello world v1.0!";
}
[ApiVersion( 2.0 )]
[Route( "helloworld" )]
public class HelloWorld2Controller : ControllerBase
{
[HttpGet]
public string Get() => "Hello world v2.0!";
}
}
Controllers with different names in the same .NET namespace
To address name collisions and provide control over how collation happens, API Versioning provides the following service:
public interface IControllerNameConvention
{
string NormalizeName( string controllerName );
string GroupName( string controllerName );
}
NormalizeName
controls how or whether a controller name is normalized. GroupName
provides the name used to group and collate on, which may not necessarily be the same as the normalized name. ControllerNameConvention
provides three implementations out-of-the-box.
ControllerNameConvention.Default
provides the default configuration which extends the original convention to have the form: [Name][#]Controller
. This means that if you already have a HelloWorldController
, you can now have a HelloWorld2Controller
and HelloWorld3Controller
. Each type name removes the Controller
suffix as well as any trailing numbers. All of these controllers would end up named and grouped HelloWorld
.
ControllerNameConvention.Original
provides an alternate configuration that retains the original naming convention. Consider that you have a type named S3Controller
. In this scenario, you do not want the 3
to be stripped away. If you have multiple versions of a such a controller, you would need your own implementation that understands this behavior or separate the types into different .NET namespaces.
ControllerNameConvention.Grouped
is a hybrid configuration the combines the Default and Original conventions. For the purposes of the name, the original convention is used. For the purposes of grouping, the default convention is used. A controller type of S3Controller
would have the name S3
, but the group name S
. The group name is only used for collation and is never displayed anywhere, so this behavior is acceptable.
If you do not want to rely on a convention, you can explicitly provide a name using the ControllerNameAttribute
. The name provided will be used verbatim for the [controller]
token, the controller name, and for grouping. This attribute is particularly useful with OData because the name of the controller must also exactly match the name of the associated entity set.
[ApiVersion( 2.0 )]
[ControllerName( "HelloWorld" )]
[Route( "[controller]" )]
public class HelloWorld2Controller : ControllerBase
{
[HttpGet]
public string Get() => "Hello world v2.0!";
}
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
{
private 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>() );
- Home
- Quick Starts
- Version Format
- Version Discovery
- Version Policies
- How to Version Your Service
- API Versioning with OData
- Configuring Your Application
- Error Responses
- API Documentation
- Extensions and Customizations
- Known Limitations
- FAQ
- Examples