Skip to content

Commit

Permalink
Add docs for api conventions and analyzers (#9453)
Browse files Browse the repository at this point in the history
* Add docs for api conventions and analyzers
Fixes #8332

* Edit pass on API conventions and analyzers content (#9574)

* Edit pass on 2.2 API docs (#9578)

* Scottaddie/open api patch (#9582)

* Edit pass on 2.2 API docs

* Resolve build warning

* Fix list formatting
  • Loading branch information
pranavkm authored and scottaddie committed Nov 14, 2018
1 parent d9d1173 commit 78b9850
Show file tree
Hide file tree
Showing 14 changed files with 479 additions and 0 deletions.
2 changes: 2 additions & 0 deletions aspnetcore/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@
## [Action return types](xref:web-api/action-return-types)
## [Format response data](xref:web-api/advanced/formatting)
## [Custom formatters](xref:web-api/advanced/custom-formatters)
## [Analyzers](xref:web-api/advanced/analyzers)
## [Conventions](xref:web-api/advanced/conventions)

# Real-time apps
## [Overview](xref:signalr/introduction)
Expand Down
69 changes: 69 additions & 0 deletions aspnetcore/web-api/advanced/analyzers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: Use web API analyzers
author: pranavkm
description: Learn about the web API analyzers in Microsoft.AspNetCore.Mvc.Api.Analyzers.
monikerRange: '>= aspnetcore-2.2'
ms.author: pranavkm
ms.custom: mvc
ms.date: 11/13/2018
uid: web-api/advanced/analyzers
---
# Use web API analyzers

ASP.NET Core 2.2 introduces the [Microsoft.AspNetCore.Mvc.Api.Analyzers](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Api.Analyzers) NuGet package containing analyzers for web APIs. The analyzers work with controllers annotated with <xref:Microsoft.AspNetCore.Mvc.ApiControllerAttribute>, while building on [API conventions](xref:web-api/advanced/conventions).

## Package installation

`Microsoft.AspNetCore.Mvc.Api.Analyzers` can be added with one of the following approaches:

### [Visual Studio](#tab/visual-studio)

* From the **Package Manager Console** window:
* Go to **View** > **Other Windows** > **Package Manager Console**.
* Navigate to the directory in which the *ApiConventions.csproj* file exists.
* Execute the following command:

```powershell
Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers
```
* From the **Manage NuGet Packages** dialog:
* Right-click the project in **Solution Explorer** > **Manage NuGet Packages**.
* Set the **Package source** to "nuget.org".
* Enter "Microsoft.AspNetCore.Mvc.Api.Analyzers" in the search box.
* Select the "Microsoft.AspNetCore.Mvc.Api.Analyzers" package from the **Browse** tab and click **Install**.
### [Visual Studio for Mac](#tab/visual-studio-mac)
* Right-click the *Packages* folder in **Solution Pad** > **Add Packages...**.
* Set the **Add Packages** window's **Source** drop-down to "nuget.org".
* Enter "Microsoft.AspNetCore.Mvc.Api.Analyzers" in the search box.
* Select the "Microsoft.AspNetCore.Mvc.Api.Analyzers" package from the results pane and click **Add Package**.
### [Visual Studio Code](#tab/visual-studio-code)
Run the following command from the **Integrated Terminal**:
```console
dotnet add ApiConventions.csproj package Microsoft.AspNetCore.Mvc.Api.Analyzers
```

### [.NET Core CLI](#tab/netcore-cli)

Run the following command:

```console
dotnet add ApiConventions.csproj package Microsoft.AspNetCore.Mvc.Api.Analyzers
```

---

## Analyzers for API conventions

Open API documents contain status codes and response types that an action may return. In ASP.NET Core MVC, attributes such as <xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute> and <xref:Microsoft.AspNetCore.Mvc.ProducesAttribute> are used to document an action. <xref:tutorials/web-api-help-pages-using-swagger> goes into further detail on documenting your API.

One of the analyzers in the package inspects controllers annotated with <xref:Microsoft.AspNetCore.Mvc.ApiControllerAttribute> and identifies actions that don't entirely document their responses. Consider the following example:

[!code-csharp[](conventions/sample/Controllers/ContactsController.cs?name=missing404docs&highlight=9)]

The preceding action documents the HTTP 200 success return type but doesn't document the HTTP 404 failure status code. The analyzer reports the missing documentation for the HTTP 404 status code as a warning. An option to fix the problem is provided.
67 changes: 67 additions & 0 deletions aspnetcore/web-api/advanced/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Use web API conventions
author: pranavkm
description: Learn about web API conventions in ASP.NET Core.
monikerRange: '>= aspnetcore-2.2'
ms.author: pranavkm
ms.custom: mvc
ms.date: 11/13/2018
uid: web-api/advanced/conventions
---
# Use web API conventions

ASP.NET Core 2.2 introduces a way to extract common [API documentation](xref:tutorials/web-api-help-pages-using-swagger) and apply it to multiple actions, controllers, or all controllers within an assembly. Web API conventions are a substitute for decorating individual actions with [[ProducesResponseType]](xref:Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute). It allows you to define the most common "conventional" return types and status codes that you return from your action with a way to select the convention method that applies to an action.

By default, ASP.NET Core MVC 2.2 ships with a set of default conventions, `Microsoft.AspNetCore.Mvc.DefaultApiConventions`. The conventions are based on the controller that ASP.NET Core scaffolds. If your actions follow the pattern that scaffolding produces, you should be successful using the default conventions.

At runtime, <xref:Microsoft.AspNetCore.Mvc.ApiExplorer> understands conventions. `ApiExplorer` is MVC's abstraction to communicate with Open API document generators. Attributes from the applied convention get associated with an action and are included in the action's Swagger documentation. API analyzers also understand conventions. If your action is unconventional (for example, it returns a status code that isn't documented by the applied convention), it produces a warning, encouraging you to document it.

[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/web-api/advanced/conventions/sample) ([how to download](xref:index#how-to-download-a-sample))

## Apply web API conventions

There are three ways to apply a convention. Conventions don't compose, each action may be associated with exactly one convention. More specific conventions (detailed below) take precedence over less specific ones. The selection is non-deterministic when two or more conventions of the same priority apply to an action. The following options exist to apply a convention to an action, from the most specific to the least specific:

1. `Microsoft.AspNetCore.Mvc.ApiConventionMethodAttribute` &mdash; Applies to individual actions and specifies the convention type and the convention method that applies. In the following sample, the convention method `Microsoft.AspNetCore.Mvc.DefaultApiConventions.Put` is applied to the `Update` action:

[!code-csharp[](conventions/sample/Controllers/ContactsConventionController.cs?name=apiconventionmethod&highlight=2-3)]

1. `Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute` applied to a controller &mdash; Applies the convention type to all actions on the controller. Convention methods are decorated with hints that determine which actions it would apply to (details as part of authoring conventions). For example:

[!code-csharp[](conventions/sample/Controllers/ContactsConventionController.cs?name=apiconventiontypeattribute)]

1. `Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute` applied to an assembly &mdash; Applies the convention type to all controllers in the current assembly. For example:

[!code-csharp[](conventions/sample/Startup.cs?name=apiconventiontypeattribute)]

## Create web API conventions

A convention is a static type with methods. These methods are annotated with `[ProducesResponseType]` or `[ProducesDefaultResponseType]` attributes.

```csharp
public static class MyAppConventions
{
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public static void Find(int id)
{
}
}
```

Applying this convention to an assembly results in the convention method applying to any action with the name `Find` and having exactly one parameter named `id`, as long as they don't have other more specific metadata attributes.

In addition to `[ProducesResponseType]` and `[ProducesDefaultResponseType]`, `[ApiConventionNameMatch]` and `[ApiConventionTypeMatch]` can be applied to the convention method that determines the methods they apply to. For example:

```csharp
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
public static void Find(
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
int id)
{ }
```

* The `Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchBehavior.Prefix` option applied to the method, indicates that the convention can match any action as long as it's prefixed with "Find". This includes methods such as `Find`, `FindPet`, or `FindById`.
* The `Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchBehavior.Suffix` applied to the parameter indicates that the convention can match methods with exactly one parameter ending in the suffix identifier. This includes parameters such as `id` or `petId`. `ApiConventionTypeMatch` can be similarly applied to types to constrain the type of the parameter. A `params[]` argument can be used to indicate remaining parameters that don't need to be explicitly matched.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.2.0-preview3-27014-02</RuntimeFrameworkVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0-preview3-35497" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Api.Analyzers" PrivateAssets="All" Version="2.2.0-preview3-35497" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ApiConventions.Models;
using Microsoft.AspNetCore.Http;

namespace ApiConventions.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ContactsController : Controller
{
public ContactsController(IContactRepository contacts)
{
Contacts = contacts;
}
public IContactRepository Contacts { get; set; }


// GET api/contacts
[HttpGet]
public IEnumerable<Contact> Get()
{
return Contacts.GetAll();
}

#region missing404docs
// GET api/contacts/{guid}
[HttpGet("{id}")]
[ProducesResponseType(typeof(Contact), StatusCodes.Status200OK)]
public IActionResult Get(string id)
{
var contact = Contacts.Get(id);
if (contact == null)
{
return NotFound();
}
return Ok(contact);
}
#endregion

// POST api/contacts
[HttpPost]
public IActionResult Post(Contact contact)
{
if (ModelState.IsValid)
{
Contacts.Add(contact);
return CreatedAtRoute("Get", new { id = contact.ID }, contact);
}
return BadRequest();
}

// PUT api/contacts/{guid}
[HttpPut("{id}")]
public IActionResult Put(string id, Contact contact)
{
if (ModelState.IsValid && id == contact.ID)
{
var contactToUpdate = Contacts.Get(id);
if (contactToUpdate != null)
{
Contacts.Update(contact);
return new NoContentResult();
}
return NotFound();
}
return BadRequest();
}

// DELETE api/contacts/{guid}
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var contact = Contacts.Get(id);
if (contact == null)
{
return NotFound();
}

Contacts.Remove(id);
return NoContent();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ApiConventions.Models;
using Microsoft.AspNetCore.Http;

namespace ApiConventions.Controllers
{
[ApiController]
#region apiconventiontypeattribute
[ApiConventionType(typeof(DefaultApiConvention))]
#endregion
[Route("api/[controller]")]
public class ContactsConventionController : Controller
{
public ContactsConventionController(IContactRepository contacts)
{
Contacts = contacts;
}
public IContactRepository Contacts { get; set; }


// GET api/contacts
[HttpGet]
public IEnumerable<Contact> Get()
{
return Contacts.GetAll();
}

// GET api/contacts/{guid}
[HttpGet("{id}")]
public ActionResult<Contact> Get(string id)
{
var contact = Contacts.Get(id);
if (contact == null)
{
return NotFound();
}
return Ok(contact);
}

// POST api/contacts
[HttpPost]
public IActionResult Post(Contact contact)
{
Contacts.Add(contact);
return CreatedAtRoute("Get", new { id = contact.ID }, contact);
}

#region apiconventionmethod
// PUT api/contacts/{guid}
[HttpPut("{id}")]
[ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Put))]
public IActionResult Update(string id, Contact contact)
{
var contactToUpdate = Contacts.Get(id);
if (contactToUpdate == null)
{
return NotFound();
}

Contacts.Update(contact);
return new NoContentResult();
}
#endregion

// DELETE api/contacts/{guid}
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var contact = Contacts.Get(id);
if (contact == null)
{
return NotFound();
}

Contacts.Remove(id);
return NoContent();
}
}
}
14 changes: 14 additions & 0 deletions aspnetcore/web-api/advanced/conventions/sample/Models/Contact.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ApiConventions.Models
{
public class Contact
{
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ApiConventions.Models
{
public class ContactRepository : IContactRepository
{
private static ConcurrentDictionary<string, Contact> _contacts =
new ConcurrentDictionary<string, Contact>();

public ContactRepository()
{
Add(new Contact() { FirstName = "Nancy", LastName = "Davolio" });
}

public void Add(Contact contact)
{
contact.ID = Guid.NewGuid().ToString();
_contacts[contact.ID] = contact;
}

public Contact Get(string id)
{
Contact contact;
_contacts.TryGetValue(id, out contact);
return contact;
}

public IEnumerable<Contact> GetAll()
{
return _contacts.Values;
}

public Contact Remove(string id)
{
Contact contact;
_contacts.TryRemove(id, out contact);
return contact;
}

public void Update(Contact contact)
{
_contacts[contact.ID] = contact;
}
}
}
Loading

0 comments on commit 78b9850

Please sign in to comment.