This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
ApiController conventions #7802
Labels
3 - Done
cost: M
Will take from 3 - 5 days to complete
PRI: 0 - Critical
Blocks a critical product path. Must be handled immediately
task
Milestone
Background
The goal of this feature is to provide good API documentation (i.e. ApiExplorer) for idiomatic APIs with minimal attribute soup. In 2.1.0, we took a stab at this - #6936 - conventions were types that
at runtime transformed
ApiDescription
. One of the difficulties with this approach is that there isn't a very good way to tell if the actual body of an action deviated from the convention. For instance, a convention could state that an action should return a 404, but could not verify the veracity of this claim. It would be nicer, if we could look at returned types in a method and infer what response codes and types are returned by an action.This brings us to the idea of using analyzers and code fixes. We could leverage analyzers to indicate when actions deviated from their stated conventions and use code fixes to "control + ." their way to success. A hard limit that analyzers enforce is that any convention that needs to be applied has to be available without executing application code. In addition, this needs to be available both at runtime, so that the runtime can transform
ApiDescription
, and at build time so analyzers \ code fixes could inspect it. One way to go about this is expressing conventions as types.API conventions as code
Explicit ApiExplorer attribute, such as
ProducesResponseTypeAttribute
, take precedence over any conventions. Once an action is associated with an ApiExplorer attribute, no conventions are applied.🌮 - We may invent a new attribute that allows specifying an exact convention to apply e.g.
ApiConvention(typeof(SomeConvention), nameof(SomeConvention.Name))
.One or more
ApiConventionsAttribute
can be applied either to the controller or as an assembly attribute. EachApiConventionsAttribute
accepts a type parameter that is the convention type.Rules for matching a convention method to an action method.
Get
from above is a match for the following actions:Get
,GetItem
,GetById
. We'll use some of the casing based matching from page handler methods and avoid matching things likeGetawayFrom()
,PostalService
as prefixes.id
from above would matchid
,personId
while not matchinggrid
.Get(object id, object _id)
to match methods such asGetPersonAddress(int personId, int addressId);
object
is used as a wildcard parameter type. Any other type requires matching. The convention methodGet(object id)
matchesGetPerson(int personId)
, bitGet(string id)
would not.Put(object id, TModel model)
to matchPut(int id, Person person)
.List(string searchTerm, params object[] any)
that matchesList(string searchTerm, int top, int page)
orList(string searchTerm, PaginationData pagination)
Default API conventions
We'd like to produce good documentation if you use the defaults. To this effect, e'll tailor a default set of conventions around code that Scaffolding produces. Here's a stripped copy of things that shipped with ASP.NET Core 2.1: https://gist.github.com/pranavkm/fbb590e031dbd0d619c3b1e01209691d. This gets us a set of conventions that looks like so:
StatusCodeAttribute
Statically knowning status codes from an
ActionResult
type is essential for analyzers to perform a diff of what was specified versus what's in code. We'll introduce aStatusCodeAttribute
that indicates what the default status code associated with anActionResult
is:e.g.
As long as the compiler can infer this type being returned from an action, we should be able to infer a possible status returned from the action. Using the default set of action results that come with MVC or using helper methods on controller base should do the right thing. That said, there are possible patterns where this approach does not work. We can think about these once we've had more user feedback, but listing them here for posterity:
return StatusCode(204);
🌮 We could tackle this unique case ofStatusCodeResult
, but not generally so.return new OkResult(result) { StatusCode = 201 };
var result = id == 0 ? (ActionResult)NotFound() : Ok("some-content"); return result;
Analyzers and code fixes
6/25 - Updated to reflect attributes for matching conventions.
The text was updated successfully, but these errors were encountered: