In this tutorial, you’ll build a simple web API for managing a list of "to-do" items. You won’t build any UI in this tutorial.1
ASP.NET Core has built-in support for MVC building Web APIs. Unifying the two frameworks makes it simpler to build apps that include both UI (HTML) and APIs, because now they share the same code base and pipeline.
Note
If you are porting an existing Web API app to ASP.NET Core, see Migrating from ASP.NET Web API1
The following diagram shows the basic design of the app.
- The client is whatever consumes the web API (browser, mobile app, and so forth). We aren’t writing a client in this tutorial. We'll useto test the app.
- A model is an object that represents the data in your application. In this case, the only model is a to-do item. Models are represented as simple C# classes (POCOs).
- A controller is an object that handles HTTP requests and creates the HTTP response. This app will have a single controller.
- To keep the tutorial simple, the app doesn’t use a database. Instead, it just keeps to-do items in memory. But we’ll still include a (trivial) data access layer, to illustrate the separation between the web API and the data layer. For a tutorial that uses a database, see.
1
Create the project
Start Visual Studio. From the File menu, select New > Project.
Select the ASP.NET Core Web Application (.NET Core) project template. Name the project
TodoApi
, clear Host in the cloud, and tap OK.
In the New ASP.NET Core Web Application (.NET Core) - TodoApi dialog, select the Web API template. Tap OK.
Add a model class
A model is an object that represents the data in your application. In this case, the only model is a to-do item.
Add a folder named "Models". In Solution Explorer, right-click the project. Select Add > New Folder. Name the folder Models.
Note
You can put model classes anywhere in your project, but the Models folder is used by convention.
Add a
TodoItem
class. Right-click the Models folder and select Add > Class. Name the class TodoItem
and tap Add.
Replace the generated code with:
Copy
C#
namespace TodoApi.Models
{
public class TodoItem
{
public string Key { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
Add a repository class
A repository is an object that encapsulates the data layer. The repository contains logic for retrieving and mapping data to an entity model. Even though the example app doesn’t use a database, it’s useful to see how you can inject a repository into your controllers. Create the repository code in the Models folder.1
Defining a repository interface named
ITodoRepository
. Use the class template (Add New Item > Class).
Copy
C#
using System.Collections.Generic;
namespace TodoApi.Models
{
public interface ITodoRepository
{
void Add(TodoItem item);
IEnumerable<TodoItem> GetAll();
TodoItem Find(string key);
TodoItem Remove(string key);
void Update(TodoItem item);
}
}
This interface defines basic CRUD operations.
Add a
TodoRepository
class that implements ITodoRepository
:
Copy
C#
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
namespace TodoApi.Models
{
public class TodoRepository : ITodoRepository
{
private static ConcurrentDictionary<string, TodoItem> _todos =
new ConcurrentDictionary<string, TodoItem>();
public TodoRepository()
{
Add(new TodoItem { Name = "Item1" });
}
public IEnumerable<TodoItem> GetAll()
{
return _todos.Values;
}
public void Add(TodoItem item)
{
item.Key = Guid.NewGuid().ToString();
_todos[item.Key] = item;
}
public TodoItem Find(string key)
{
TodoItem item;
_todos.TryGetValue(key, out item);
return item;
}
public TodoItem Remove(string key)
{
TodoItem item;
_todos.TryRemove(key, out item);
return item;
}
public void Update(TodoItem item)
{
_todos[item.Key] = item;
}
}
}
Build the app to verify you don't have any compiler errors.
Register the repository
By defining a repository interface, we can decouple the repository class from the MVC controller that uses it. Instead of instantiating a
TodoRepository
inside the controller we will inject an ITodoRepository
using the built-in support in ASP.NET Core for .
This approach makes it easier to unit test your controllers. Unit tests should inject a mock or stub version of
ITodoRepository
. That way, the test narrowly targets the controller logic and not the data access layer.
In order to inject the repository into the controller, we need to register it with the DI container. Open the Startup.cs file. Add the following using directive:
Copy
C#
using TodoApi.Models;
In the
ConfigureServices
method, add the highlighted code:3
Copy
C#
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSingleton<ITodoRepository, TodoRepository>();
}
Add a controller
In Solution Explorer, right-click the Controllers folder. Select Add > New Item. In the Add New Item dialog, select the Web API Controller Class template. Name the class
TodoController
.
Replace the generated code with the following:
Copy
C#
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TodoApi.Models;
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
public class TodoController : Controller
{
public TodoController(ITodoRepository todoItems)
{
TodoItems = todoItems;
}
public ITodoRepository TodoItems { get; set; }
}
}
This defines an empty controller class. In the next sections, we'll add methods to implement the API.
Getting to-do items
To get to-do items, add the following methods to the
TodoController
class.
Copy
C#
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return TodoItems.GetAll();
}
[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
{
var item = TodoItems.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
These methods implement the two GET methods:
GET /api/todo
GET /api/todo/{id}
Here is an example HTTP response for the
GetAll
method:
Copy
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
Date: Thu, 18 Jun 2015 20:51:10 GMT
Content-Length: 82
[{"Key":"4f67d7c5-a2a9-4aae-b030-16003dd829ae","Name":"Item1","IsComplete":false}]
Later in the tutorial I'll show how you can view the HTTP response using
.Routing and URL paths
The
[HttpGet]
attribute (HttpGetAttribute
) specifies an HTTP GET method. The URL path for each method is constructed as follows:- Take the template string in the controller’s route attribute,
[Route("api/[controller]")]
- Replace "[Controller]" with the name of the controller, which is the controller class name minus the "Controller" suffix. For this sample, the controller class name is TodoController and the root name is "todo". ASP.NET Core is not case sensitive.
- If the
[HttpGet]
attribute has a template string, append that to the path. This sample doesn't use a template string.
In the
GetById
method:
Copy
C#
[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
"{id}"
is a placeholder variable for the ID of the todo
item. When GetById
is invoked, it assigns the value of "{id}" in the URL to the method's id
parameter.Name = "GetTodo"
creates a named route and allows you to link to this route in an HTTP Response. I'll explain it with an example later. See Routing to Controller Actions for detailed information.Return values
The
GetAll
method returns an IEnumerable
. MVC automatically serializes the object to and writes the JSON into the body of the response message. The response code for this method is 200, assuming there are no unhandled exceptions. (Unhandled exceptions are translated into 5xx errors.)
In contrast, the
GetById
method returns the more general IActionResult
type, which represents a wide range of return types. GetById
has two different return types:- If no item matches the requested ID, the method returns a 404 error. This is done by returning
NotFound
. - Otherwise, the method returns 200 with a JSON response body. This is done by returning an
ObjectResult
Launch the app
In Visual Studio, press CTRL+F5 to launch the app. Visual Studio launches a browser and navigates to
http://localhost:port/api/values
, where port is a randomly chosen port number. If you're using Chrome, Edge or Firefox, the data will be displayed. If you're using IE, IE will prompt to you open or save the values.json file. Navigate to the Todo
controller we just created http://localhost:port/api/todo
.4Implement the other CRUD operations
We'll add
Create
, Update
, and Delete
methods to the controller. These are variations on a theme, so I'll just show the code and highlight the main differences. Build the project after adding or changing code.Create
Copy
C#
[HttpPost]
public IActionResult Create([FromBody] TodoItem item)
{
if (item == null)
{
return BadRequest();
}
TodoItems.Add(item);
return CreatedAtRoute("GetTodo", new { id = item.Key }, item);
}
This is an HTTP POST method, indicated by the
[HttpPost]
attribute. The [FromBody]
attribute tells MVC to get the value of the to-do item from the body of the HTTP request.
The
CreatedAtRoute
method returns a 201 response, which is the standard response for an HTTP POST method that creates a new resource on the server. CreatedAtRoute
also adds a Location header to the response. The Location header specifies the URI of the newly created to-do item. See .Use Postman to send a Create request
- Set the HTTP method to
POST
- Tap the Body radio button
- Tap the raw radio button
- Set the type to JSON
- In the key-value editor, enter a Todo item such as
{"Name":"<your to-do item>"}
- Tap Send
Tap the Headers tab and copy the Location header:
You can use the Location header URI to access the resource you just created. Recall the
GetById
method created the "GetTodo"
named route:
Copy
C#
[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
Update
Copy
C#
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody] TodoItem item)
{
if (item == null || item.Key != id)
{
return BadRequest();
}
var todo = TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
TodoItems.Update(item);
return new NoContentResult();
}
Update
is similar to Create
, but uses HTTP PUT. The response is . According to the HTTP spec, a PUT request requires the client to send the entire updated entity, not just the deltas. To support partial updates, use HTTP PATCH.Delete
Copy
C#
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var todo = TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
TodoItems.Remove(id);
return new NoContentResult();
}
The response is .
No comments:
Post a Comment