Controller Naming Conventions
There are a few implicit conventions to be aware of.
Always Versioned
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.
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 )]
[RoutePrefix( "helloworld" )]
public class HelloWorldController : ApiController
{
[Route]
public string Get() => "Hello world v1.0!";
}
}
namespace My.Services.V2
{
[ApiVersion( 2.0 )]
[RoutePrefix( "helloworld" )]
public class HelloWorldController : ApiController
{
[Route]
public string Get() => "Hello world v2.0!";
}
}
Controllers separated by .NET namespace
namespace My.Services.Controllers
{
[ApiVersion( 1.0 )]
[RoutePrefix( "helloworld" )]
public class HelloWorldController : ApiController
{
[Route]
public string Get() => "Hello world v1.0!";
}
[ApiVersion( 2.0 )]
[RoutePrefix( "helloworld" )]
public class HelloWorld2Controller : ApiController
{
[Route]
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.
Default
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.
Original
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.
Grouped
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.
Attribute
If you do not want to rely on a convention, you can explicitly provide a name using the ControllerNameAttribute. 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 )]
[RoutePrefix( "helloworld" )]
[ControllerName( "HelloWorld" )]
public class HelloWorld2Controller : ControllerBase
{
[Route]
public string Get() => "Hello world v2.0!";
}