Tutorial: Create a web API with ASP.NET Core
By Rick Anderson, Kirk Larkin, and Mike Wasson
This tutorial teaches the basics of building a web API with ASP.NET Core.
In this tutorial, you learn how to:
- Create a web API project.
- Add a model class and a database context.
- Scaffold a controller with CRUD methods.
- Configure routing, URL paths, and return values.
- Call the web API with Postman.
At the end, you have a web API that can manage "to-do" items stored in a database.
Overview
This tutorial creates the following API:
API | Description | Request body | Response body |
---|---|---|---|
GET /api/TodoItems |
Get all to-do items | None | Array of to-do items |
GET /api/TodoItems/{id} |
Get an item by ID | None | To-do item |
POST /api/TodoItems |
Add a new item | To-do item | To-do item |
PUT /api/TodoItems/{id} |
Update an existing item | To-do item | None |
DELETE /api/TodoItems/{id} |
Delete an item | None | None |
The following diagram shows the design of the app.
Prerequisites
- Visual Studio 2019 16.8 or later with the ASP.NET and web development workload
- .NET 5.0 SDK or later
Create a web project
- From the File menu, select New > Project.
- Select the ASP.NET Core Web API template and click Next.
- Name the project TodoApi and click Create.
- In the Create a new ASP.NET Core Web Application dialog, confirm that .NET Core and ASP.NET Core 5.0 are selected. Select the API template and click Create.
Test the project
The project template creates a WeatherForecast
API with support for Swagger.
Press Ctrl+F5 to run without the debugger.
Visual Studio displays the following dialog:
Select Yes if you trust the IIS Express SSL certificate.
The following dialog is displayed:
Select Yes if you agree to trust the development certificate.
For information on trusting the Firefox browser, see Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certificate error.
Visual Studio launches:
- The IIS Express web server.
- The default browser and navigates to
https://localhost:<port>/swagger/index.html
, where<port>
is a randomly chosen port number.
The Swagger page /swagger/index.html
is displayed. Select GET > Try it out > Execute. The page displays:
- The Curl command to test the WeatherForecast API.
- The URL to test the WeatherForecast API.
- The response code, body, and headers.
- A drop down list box with media types and the example value and schema.
If the Swagger page doesn't appear, see this GitHub issue.
Swagger is used to generate useful documentation and help pages for web APIs. This tutorial focuses on creating a web API. For more information on Swagger, see ASP.NET Core web API documentation with Swagger / OpenAPI.
Copy and paste the Request URL in the browser: https://localhost:<port>/WeatherForecast
JSON similar to the following is returned:
[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]
Update the launchUrl
In Properties\launchSettings.json, update launchUrl
from "swagger"
to "api/TodoItems"
:
"launchUrl": "api/TodoItems",
Because Swagger has been removed, the preceding markup changes the URL that is launched to the GET method of the controller added in the following sections.
Add a model class
A model is a set of classes that represent the data that the app manages. The model for this app is a single TodoItem
class.
In Solution Explorer, right-click the project. Select Add > New Folder. Name the folder Models.
Right-click the Models folder and select Add > Class. Name the class TodoItem and select Add.
Replace the template code with the following:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
The Id
property functions as the unique key in a relational database.
Model classes can go anywhere in the project, but the Models folder is used by convention.
Add a database context
The database context is the main class that coordinates Entity Framework functionality for a data model. This class is created by deriving from the Microsoft.EntityFrameworkCore.DbContext class.
Add NuGet packages
- From the Tools menu, select NuGet Package Manager > Manage NuGet Packages for Solution.
- Select the Browse tab, and then enter
Microsoft.EntityFrameworkCore.InMemory
in the search box. - Select
Microsoft.EntityFrameworkCore.InMemory
in the left pane. - Select the Project check box in the right pane and then select Install.
Add the TodoContext database context
- Right-click the Models folder and select Add > Class. Name the class TodoContext and click Add.
Enter the following code:
using Microsoft.EntityFrameworkCore; namespace TodoApi.Models { public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } } }
Register the database context
In ASP.NET Core, services such as the DB context must be registered with the dependency injection (DI) container. The container provides the service to controllers.
Update Startup.cs with the following code:
// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
The preceding code:
- Removes the Swagger calls.
- Removes unused
using
declarations. - Adds the database context to the DI container.
- Specifies that the database context will use an in-memory database.
Scaffold a controller
Right-click the Controllers folder.
Select Add > New Scaffolded Item.
Select API Controller with actions, using Entity Framework, and then select Add.
In the Add API Controller with actions, using Entity Framework dialog:
- Select TodoItem (TodoApi.Models) in the Model class.
- Select TodoContext (TodoApi.Models) in the Data context class.
- Select Add.
The generated code:
- Marks the class with the
[ApiController]
attribute. This attribute indicates that the controller responds to web API requests. For information about specific behaviors that the attribute enables, see Create web APIs with ASP.NET Core. - Uses DI to inject the database context (
TodoContext
) into the controller. The database context is used in each of the CRUD methods in the controller.
The ASP.NET Core templates for:
- Controllers with views include
[action]
in the route template. - API controllers don't include
[action]
in the route template.
When the [action]
token isn't in the route template, the action name is excluded from the route. That is, the action's associated method name isn't used in the matching route.
Update the PostTodoItem create method
Update the return statement in the PostTodoItem
to use the nameof operator:
// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
//return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}
The preceding code is an HTTP POST method, as indicated by the [HttpPost]
attribute. The method gets the value of the to-do item from the body of the HTTP request.
For more information, see Attribute routing with Http[Verb] attributes.
The CreatedAtAction method:
- Returns an HTTP 201 status code if successful. HTTP 201 is the standard response for an HTTP POST method that creates a new resource on the server.
- Adds a Location header to the response. The
Location
header specifies the URI of the newly created to-do item. For more information, see 10.2.2 201 Created. - References the
GetTodoItem
action to create theLocation
header's URI. The C#nameof
keyword is used to avoid hard-coding the action name in theCreatedAtAction
call.
Install Postman
This tutorial uses Postman to test the web API.
- Install Postman
- Start the web app.
- Start Postman.
- Disable SSL certificate verification
- From File > Settings (General tab), disable SSL certificate verification.
Warning
Re-enable SSL certificate verification after testing the controller.
- From File > Settings (General tab), disable SSL certificate verification.
Test PostTodoItem with Postman
Create a new request.
Set the HTTP method to
POST
.Set the URI to
https://localhost:<port>/api/TodoItems
. For example,https://localhost:5001/api/TodoItems
.Select the Body tab.
Select the raw radio button.
Set the type to JSON (application/json).
In the request body enter JSON for a to-do item:
{ "name":"walk dog", "isComplete":true }
Select Send.
Test the location header URI
The location header URI can be tested in the browser. Copy and paste the location header URI into the browser.
To test in Postman:
Select the Headers tab in the Response pane.
Copy the Location header value:
Set the HTTP method to
GET
.Set the URI to
https://localhost:<port>/api/TodoItems/1
. For example,https://localhost:5001/api/TodoItems/1
.Select Send.
Examine the GET methods
Two GET endpoints are implemented:
GET /api/TodoItems
GET /api/TodoItems/{id}
Test the app by calling the two endpoints from a browser or Postman. For example:
https://localhost:5001/api/TodoItems
https://localhost:5001/api/TodoItems/1
A response similar to the following is produced by the call to GetTodoItems
:
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Test Get with Postman
- Create a new request.
- Set the HTTP method to GET.
- Set the request URI to
https://localhost:<port>/api/TodoItems
. For example,https://localhost:5001/api/TodoItems
. - Set Two pane view in Postman.
- Select Send.
This app uses an in-memory database. If the app is stopped and started, the preceding GET request will not return any data. If no data is returned, POST data to the app.
Routing and URL paths
The [HttpGet]
attribute denotes a method that responds to an HTTP GET request. The URL path for each method is constructed as follows:
Start with the template string in the controller's
Route
attribute:[Route("api/[controller]")] [ApiController] public class TodoItemsController : ControllerBase { private readonly TodoContext _context; public TodoItemsController(TodoContext context) { _context = context; }
Replace
[controller]
with the name of the controller, which by convention is the controller class name minus the "Controller" suffix. For this sample, the controller class name is TodoItemsController, so the controller name is "TodoItems". ASP.NET Core routing is case insensitive.If the
[HttpGet]
attribute has a route template (for example,[HttpGet("products")]
), append that to the path. This sample doesn't use a template. For more information, see Attribute routing with Http[Verb] attributes.
In the following GetTodoItem
method, "{id}"
is a placeholder variable for the unique identifier of the to-do item. When GetTodoItem
is invoked, the value of "{id}"
in the URL is provided to the method in its id
parameter.
// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
Return values
The return type of the GetTodoItems
and GetTodoItem
methods is ActionResult<T> type. ASP.NET Core automatically serializes the object to JSON and writes the JSON into the body of the response message. The response code for this return type is 200 OK, assuming there are no unhandled exceptions. Unhandled exceptions are translated into 5xx errors.
ActionResult
return types can represent a wide range of HTTP status codes. For example, GetTodoItem
can return two different status values:
- If no item matches the requested ID, the method returns a 404 status NotFound error code.
- Otherwise, the method returns 200 with a JSON response body. Returning
item
results in an HTTP 200 response.
The PutTodoItem method
Examine the PutTodoItem
method:
// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
PutTodoItem
is similar to PostTodoItem
, except it uses HTTP PUT. The response is 204 (No Content). According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the changes. To support partial updates, use HTTP PATCH.
If you get an error calling PutTodoItem
, call GET
to ensure there's an item in the database.
Test the PutTodoItem method
This sample uses an in-memory database that must be initialized each time the app is started. There must be an item in the database before you make a PUT call. Call GET to ensure there's an item in the database before making a PUT call.
Update the to-do item that has Id = 1 and set its name to "feed fish"
:
{
"Id":1,
"name":"feed fish",
"isComplete":true
}
The following image shows the Postman update:
The DeleteTodoItem method
Examine the DeleteTodoItem
method:
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
Test the DeleteTodoItem method
Use Postman to delete a to-do item:
- Set the method to
DELETE
. - Set the URI of the object to delete (for example
https://localhost:5001/api/TodoItems/1
). - Select Send.
Prevent over-posting
Currently the sample app exposes the entire TodoItem
object. Production apps typically limit the data that's input and returned using a subset of the model. There are multiple reasons behind this and security is a major one. The subset of a model is usually referred to as a Data Transfer Object (DTO), input model, or view model. DTO is used in this article.
A DTO may be used to:
- Prevent over-posting.
- Hide properties that clients are not supposed to view.
- Omit some properties in order to reduce payload size.
- Flatten object graphs that contain nested objects. Flattened object graphs can be more convenient for clients.
To demonstrate the DTO approach, update the TodoItem
class to include a secret field:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
public string Secret { get; set; }
}
}
The secret field needs to be hidden from this app, but an administrative app could choose to expose it.
Verify you can post and get the secret field.
Create a DTO model:
public class TodoItemDTO
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
Update the TodoItemsController
to use TodoItemDTO
:
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return ItemToDTO(todoItem);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
if (id != todoItemDTO.Id)
{
return BadRequest();
}
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
todoItem.Name = todoItemDTO.Name;
todoItem.IsComplete = todoItemDTO.IsComplete;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}
return NoContent();
}
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
private bool TodoItemExists(long id) =>
_context.TodoItems.Any(e => e.Id == id);
private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
Verify you can't post or get the secret field.
Call the web API with JavaScript
In this tutorial, you learn how to:
- Create a web API project.
- Add a model class and a database context.
- Scaffold a controller with CRUD methods.
- Configure routing, URL paths, and return values.
- Call the web API with Postman.
At the end, you have a web API that can manage "to-do" items stored in a database.
Overview
This tutorial creates the following API:
API | Description | Request body | Response body |
---|---|---|---|
GET /api/TodoItems |
Get all to-do items | None | Array of to-do items |
GET /api/TodoItems/{id} |
Get an item by ID | None | To-do item |
POST /api/TodoItems |
Add a new item | To-do item | To-do item |
PUT /api/TodoItems/{id} |
Update an existing item | To-do item | None |
DELETE /api/TodoItems/{id} |
Delete an item | None | None |
The following diagram shows the design of the app.
Prerequisites
Visual Studio 2019 16.4 or later with the ASP.NET and web development workload
Create a web project
- From the File menu, select New > Project.
- Select the ASP.NET Core Web Application template and click Next.
- Name the project TodoApi and click Create.
- In the Create a new ASP.NET Core Web Application dialog, confirm that .NET Core and ASP.NET Core 3.1 are selected. Select the API template and click Create.
Test the API
The project template creates a WeatherForecast
API. Call the Get
method from a browser to test the app.
Press Ctrl+F5 to run the app. Visual Studio launches a browser and navigates to https://localhost:<port>/WeatherForecast
, where <port>
is a randomly chosen port number.
If you get a dialog box that asks if you should trust the IIS Express certificate, select Yes. In the Security Warning dialog that appears next, select Yes.
JSON similar to the following is returned:
[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]
Add a model class
A model is a set of classes that represent the data that the app manages. The model for this app is a single TodoItem
class.
In Solution Explorer, right-click the project. Select Add > New Folder. Name the folder Models.
Right-click the Models folder and select Add > Class. Name the class TodoItem and select Add.
Replace the template code with the following code:
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
The Id
property functions as the unique key in a relational database.
Model classes can go anywhere in the project, but the Models folder is used by convention.
Add a database context
The database context is the main class that coordinates Entity Framework functionality for a data model. This class is created by deriving from the Microsoft.EntityFrameworkCore.DbContext
class.
Add NuGet packages
- From the Tools menu, select NuGet Package Manager > Manage NuGet Packages for Solution.
- Select the Browse tab, and then enter Microsoft.EntityFrameworkCore.InMemory in the search box.
- Select Microsoft.EntityFrameworkCore.InMemory in the left pane.
- Select the Project check box in the right pane and then select Install.
Add the TodoContext database context
- Right-click the Models folder and select Add > Class. Name the class TodoContext and click Add.
Enter the following code:
using Microsoft.EntityFrameworkCore; namespace TodoApi.Models { public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } } }
Register the database context
In ASP.NET Core, services such as the DB context must be registered with the dependency injection (DI) container. The container provides the service to controllers.
Update Startup.cs with the following highlighted code:
// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
The preceding code:
- Removes unused
using
declarations. - Adds the database context to the DI container.
- Specifies that the database context will use an in-memory database.
Scaffold a controller
Right-click the Controllers folder.
Select Add > New Scaffolded Item.
Select API Controller with actions, using Entity Framework, and then select Add.
In the Add API Controller with actions, using Entity Framework dialog:
- Select TodoItem (TodoApi.Models) in the Model class.
- Select TodoContext (TodoApi.Models) in the Data context class.
- Select Add.
The generated code:
- Marks the class with the
[ApiController]
attribute. This attribute indicates that the controller responds to web API requests. For information about specific behaviors that the attribute enables, see Create web APIs with ASP.NET Core. - Uses DI to inject the database context (
TodoContext
) into the controller. The database context is used in each of the CRUD methods in the controller.
The ASP.NET Core templates for:
- Controllers with views include
[action]
in the route template. - API controllers don't include
[action]
in the route template.
When the [action]
token isn't in the route template, the action name is excluded from the route. That is, the action's associated method name isn't used in the matching route.
Examine the PostTodoItem create method
Replace the return statement in the PostTodoItem
to use the nameof operator:
// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
//return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}
The preceding code is an HTTP POST method, as indicated by the [HttpPost]
attribute. The method gets the value of the to-do item from the body of the HTTP request.
For more information, see Attribute routing with Http[Verb] attributes.
The CreatedAtAction method:
- Returns an HTTP 201 status code if successful. HTTP 201 is the standard response for an HTTP POST method that creates a new resource on the server.
- Adds a Location header to the response. The
Location
header specifies the URI of the newly created to-do item. For more information, see 10.2.2 201 Created. - References the
GetTodoItem
action to create theLocation
header's URI. The C#nameof
keyword is used to avoid hard-coding the action name in theCreatedAtAction
call.
Install Postman
This tutorial uses Postman to test the web API.
- Install Postman
- Start the web app.
- Start Postman.
- Disable SSL certificate verification
- From File > Settings (General tab), disable SSL certificate verification.
Warning
Re-enable SSL certificate verification after testing the controller.
- From File > Settings (General tab), disable SSL certificate verification.
Test PostTodoItem with Postman
Create a new request.
Set the HTTP method to
POST
.Set the URI to
https://localhost:<port>/api/TodoItems
. For example,https://localhost:5001/api/TodoItems
.Select the Body tab.
Select the raw radio button.
Set the type to JSON (application/json).
In the request body enter JSON for a to-do item:
{ "name":"walk dog", "isComplete":true }
Select Send.
Test the location header URI with Postman
Select the Headers tab in the Response pane.
Copy the Location header value:
Set the HTTP method to
GET
.Set the URI to
https://localhost:<port>/api/TodoItems/1
. For example,https://localhost:5001/api/TodoItems/1
.Select Send.
Examine the GET methods
These methods implement two GET endpoints:
GET /api/TodoItems
GET /api/TodoItems/{id}
Test the app by calling the two endpoints from a browser or Postman. For example:
https://localhost:5001/api/TodoItems
https://localhost:5001/api/TodoItems/1
A response similar to the following is produced by the call to GetTodoItems
:
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Test Get with Postman
- Create a new request.
- Set the HTTP method to GET.
- Set the request URI to
https://localhost:<port>/api/TodoItems
. For example,https://localhost:5001/api/TodoItems
. - Set Two pane view in Postman.
- Select Send.
This app uses an in-memory database. If the app is stopped and started, the preceding GET request will not return any data. If no data is returned, POST data to the app.
Routing and URL paths
The [HttpGet]
attribute denotes a method that responds to an HTTP GET request. The URL path for each method is constructed as follows:
Start with the template string in the controller's
Route
attribute:[Route("api/[controller]")] [ApiController] public class TodoItemsController : ControllerBase { private readonly TodoContext _context; public TodoItemsController(TodoContext context) { _context = context; }
Replace
[controller]
with the name of the controller, which by convention is the controller class name minus the "Controller" suffix. For this sample, the controller class name is TodoItemsController, so the controller name is "TodoItems". ASP.NET Core routing is case insensitive.If the
[HttpGet]
attribute has a route template (for example,[HttpGet("products")]
), append that to the path. This sample doesn't use a template. For more information, see Attribute routing with Http[Verb] attributes.
In the following GetTodoItem
method, "{id}"
is a placeholder variable for the unique identifier of the to-do item. When GetTodoItem
is invoked, the value of "{id}"
in the URL is provided to the method in its id
parameter.
// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
Return values
The return type of the GetTodoItems
and GetTodoItem
methods is ActionResult<T> type. ASP.NET Core automatically serializes the object to JSON and writes the JSON into the body of the response message. The response code for this return type is 200, assuming there are no unhandled exceptions. Unhandled exceptions are translated into 5xx errors.
ActionResult
return types can represent a wide range of HTTP status codes. For example, GetTodoItem
can return two different status values:
- If no item matches the requested ID, the method returns a 404 NotFound error code.
- Otherwise, the method returns 200 with a JSON response body. Returning
item
results in an HTTP 200 response.
The PutTodoItem method
Examine the PutTodoItem
method:
// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
PutTodoItem
is similar to PostTodoItem
, except it uses HTTP PUT. The response is 204 (No Content). According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the changes. To support partial updates, use HTTP PATCH.
If you get an error calling PutTodoItem
, call GET
to ensure there's an item in the database.
Test the PutTodoItem method
This sample uses an in-memory database that must be initialized each time the app is started. There must be an item in the database before you make a PUT call. Call GET to ensure there's an item in the database before making a PUT call.
Update the to-do item that has Id = 1 and set its name to "feed fish":
{
"id":1,
"name":"feed fish",
"isComplete":true
}
The following image shows the Postman update:
The DeleteTodoItem method
Examine the DeleteTodoItem
method:
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return todoItem;
}
Test the DeleteTodoItem method
Use Postman to delete a to-do item:
- Set the method to
DELETE
. - Set the URI of the object to delete (for example
https://localhost:5001/api/TodoItems/1
). - Select Send.
Prevent over-posting
Currently the sample app exposes the entire TodoItem
object. Production apps typically limit the data that's input and returned using a subset of the model. There are multiple reasons behind this and security is a major one. The subset of a model is usually referred to as a Data Transfer Object (DTO), input model, or view model. DTO is used in this article.
A DTO may be used to:
- Prevent over-posting.
- Hide properties that clients are not supposed to view.
- Omit some properties in order to reduce payload size.
- Flatten object graphs that contain nested objects. Flattened object graphs can be more convenient for clients.
To demonstrate the DTO approach, update the TodoItem
class to include a secret field:
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
public string Secret { get; set; }
}
The secret field needs to be hidden from this app, but an administrative app could choose to expose it.
Verify you can post and get the secret field.
Create a DTO model:
public class TodoItemDTO
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
Update the TodoItemsController
to use TodoItemDTO
:
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return ItemToDTO(todoItem);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
if (id != todoItemDTO.Id)
{
return BadRequest();
}
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
todoItem.Name = todoItemDTO.Name;
todoItem.IsComplete = todoItemDTO.IsComplete;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}
return NoContent();
}
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
private bool TodoItemExists(long id) =>
_context.TodoItems.Any(e => e.Id == id);
private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}
Verify you can't post or get the secret field.
Call the web API with JavaScript
In this tutorial, you learn how to:
- Create a web API project.
- Add a model class and a database context.
- Add a controller.
- Add CRUD methods.
- Configure routing and URL paths.
- Specify return values.
- Call the web API with Postman.
- Call the web API with JavaScript.
At the end, you have a web API that can manage "to-do" items stored in a relational database.
Overview 2.1
This tutorial creates the following API:
API | Description | Request body | Response body |
---|---|---|---|
GET /api/TodoItems | Get all to-do items | None | Array of to-do items |
GET /api/TodoItems/{id} | Get an item by ID | None | To-do item |
POST /api/TodoItems | Add a new item | To-do item | To-do item |
PUT /api/TodoItems/{id} | Update an existing item | To-do item | None |
DELETE /api/TodoItems/{id} | Delete an item | None | None |
The following diagram shows the design of the app.
Prerequisites 2.1
- Visual Studio 2019 with the ASP.NET and web development workload
- .NET Core SDK 2.2 or later
Warning
If you use Visual Studio 2017, see dotnet/sdk issue #3124 for information about .NET Core SDK versions that don't work with Visual Studio.
Create a web project 2.1
- From the File menu, select New > Project.
- Select the ASP.NET Core Web Application template and click Next.
- Name the project TodoApi and click Create.
- In the Create a new ASP.NET Core Web Application dialog, confirm that .NET Core and ASP.NET Core 2.2 are selected. Select the API template and click Create. Don't select Enable Docker Support.
Test the API 2.1
The project template creates a values
API. Call the Get
method from a browser to test the app.
Press Ctrl+F5 to run the app. Visual Studio launches a browser and navigates to https://localhost:<port>/api/values
, where <port>
is a randomly chosen port number.
If you get a dialog box that asks if you should trust the IIS Express certificate, select Yes. In the Security Warning dialog that appears next, select Yes.
The following JSON is returned:
["value1","value2"]
Add a model class 2.1
A model is a set of classes that represent the data that the app manages. The model for this app is a single TodoItem
class.
In Solution Explorer, right-click the project. Select Add > New Folder. Name the folder Models.
Right-click the Models folder and select Add > Class. Name the class TodoItem and select Add.
Replace the template code with the following code:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
The Id
property functions as the unique key in a relational database.
Model classes can go anywhere in the project, but the Models folder is used by convention.
Add a database context 2.1
The database context is the main class that coordinates Entity Framework functionality for a data model. This class is created by deriving from the Microsoft.EntityFrameworkCore.DbContext
class.
- Right-click the Models folder and select Add > Class. Name the class TodoContext and click Add.
Replace the template code with the following code:
using Microsoft.EntityFrameworkCore; namespace TodoApi.Models { public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } } }
Register the database context 2.1
In ASP.NET Core, services such as the DB context must be registered with the dependency injection (DI) container. The container provides the service to controllers.
Update Startup.cs with the following highlighted code:
// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;
namespace TodoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the
//container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP
//request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
The preceding code:
- Removes unused
using
declarations. - Adds the database context to the DI container.
- Specifies that the database context will use an in-memory database.
Add a controller 2.1
Right-click the Controllers folder.
Select Add > New Item.
In the Add New Item dialog, select the API Controller Class template.
Name the class TodoController, and select Add.
Replace the template code with the following code:
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using TodoApi.Models; namespace TodoApi.Controllers { [Route("api/[controller]")] [ApiController] public class TodoController : ControllerBase { private readonly TodoContext _context; public TodoController(TodoContext context) { _context = context; if (_context.TodoItems.Count() == 0) { // Create a new TodoItem if collection is empty, // which means you can't delete all TodoItems. _context.TodoItems.Add(new TodoItem { Name = "Item1" }); _context.SaveChanges(); } } } }
The preceding code:
- Defines an API controller class without methods.
- Marks the class with the
[ApiController]
attribute. This attribute indicates that the controller responds to web API requests. For information about specific behaviors that the attribute enables, see Create web APIs with ASP.NET Core. - Uses DI to inject the database context (
TodoContext
) into the controller. The database context is used in each of the CRUD methods in the controller. - Adds an item named
Item1
to the database if the database is empty. This code is in the constructor, so it runs every time there's a new HTTP request. If you delete all items, the constructor createsItem1
again the next time an API method is called. So it may look like the deletion didn't work when it actually did work.
Add Get methods 2.1
To provide an API that retrieves to-do items, add the following methods to the TodoController
class:
// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
These methods implement two GET endpoints:
GET /api/todo
GET /api/todo/{id}
Stop the app if it's still running. Then run it again to include the latest changes.
Test the app by calling the two endpoints from a browser. For example:
https://localhost:<port>/api/todo
https://localhost:<port>/api/todo/1
The following HTTP response is produced by the call to GetTodoItems
:
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Routing and URL paths 2.1
The [HttpGet]
attribute denotes a method that responds to an HTTP GET request. The URL path for each method is constructed as follows:
Start with the template string in the controller's
Route
attribute:namespace TodoApi.Controllers { [Route("api/[controller]")] [ApiController] public class TodoController : ControllerBase { private readonly TodoContext _context;
Replace
[controller]
with the name of the controller, which by convention is the controller class name minus the "Controller" suffix. For this sample, the controller class name is TodoController, so the controller name is "todo". ASP.NET Core routing is case insensitive.If the
[HttpGet]
attribute has a route template (for example,[HttpGet("products")]
), append that to the path. This sample doesn't use a template. For more information, see Attribute routing with Http[Verb] attributes.
In the following GetTodoItem
method, "{id}"
is a placeholder variable for the unique identifier of the to-do item. When GetTodoItem
is invoked, the value of "{id}"
in the URL is provided to the method in itsid
parameter.
// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
Return values 2.1
The return type of the GetTodoItems
and GetTodoItem
methods is ActionResult<T> type. ASP.NET Core automatically serializes the object to JSON and writes the JSON into the body of the response message. The response code for this return type is 200, assuming there are no unhandled exceptions. Unhandled exceptions are translated into 5xx errors.
ActionResult
return types can represent a wide range of HTTP status codes. For example, GetTodoItem
can return two different status values:
- If no item matches the requested ID, the method returns a 404 NotFound error code.
- Otherwise, the method returns 200 with a JSON response body. Returning
item
results in an HTTP 200 response.
Test the GetTodoItems method 2.1
This tutorial uses Postman to test the web API.
- Install Postman.
- Start the web app.
- Start Postman.
- Disable SSL certificate verification.
- From File > Settings (General tab), disable SSL certificate verification.
Warning
Re-enable SSL certificate verification after testing the controller.
- Create a new request.
- Set the HTTP method to GET.
- Set the request URI to
https://localhost:<port>/api/todo
. For example,https://localhost:5001/api/todo
.
- Set Two pane view in Postman.
- Select Send.
Add a Create method 2.1
Add the following PostTodoItem
method inside of Controllers/TodoController.cs:
// POST: api/Todo
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
_context.TodoItems.Add(item);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
}
The preceding code is an HTTP POST method, as indicated by the [HttpPost]
attribute. The method gets the value of the to-do item from the body of the HTTP request.
The CreatedAtAction
method:
Returns an HTTP 201 status code, if successful. HTTP 201 is the standard response for an HTTP POST method that creates a new resource on the server.
Adds a
Location
header to the response. TheLocation
header specifies the URI of the newly created to-do item. For more information, see 10.2.2 201 Created.References the
GetTodoItem
action to create theLocation
header's URI. The C#nameof
keyword is used to avoid hard-coding the action name in theCreatedAtAction
call.// GET: api/Todo/5 [HttpGet("{id}")] public async Task<ActionResult<TodoItem>> GetTodoItem(long id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } return todoItem; }
Test the PostTodoItem method 2.1
Build the project.
In Postman, set the HTTP method to
POST
.Set the URI to
https://localhost:<port>/api/Todo
. For example,https://localhost:5001/api/Todo
.Select the Body tab.
Select the raw radio button.
Set the type to JSON (application/json).
In the request body enter JSON for a to-do item:
{ "name":"walk dog", "isComplete":true }
Select Send.
If you get a 405 Method Not Allowed error, it's probably the result of not compiling the project after adding the
PostTodoItem
method.
Test the location header URI 2.1
Select the Headers tab in the Response pane.
Copy the Location header value:
Set the method to GET. * Set the URI to
https://localhost:<port>/api/TodoItems/2
. For example,https://localhost:5001/api/TodoItems/2
.Select Send.
Add a PutTodoItem method 2.1
Add the following PutTodoItem
method:
// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
if (id != item.Id)
{
return BadRequest();
}
_context.Entry(item).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
PutTodoItem
is similar to PostTodoItem
, except it uses HTTP PUT. The response is 204 (No Content). According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the changes. To support partial updates, use HTTP PATCH.
If you get an error calling PutTodoItem
, call GET
to ensure there's an item in the database.
Test the PutTodoItem method 2.1
This sample uses an in-memory database that must be initialized each time the app is started. There must be an item in the database before you make a PUT call. Call GET to ensure there's an item in the database before making a PUT call.
Update the to-do item that has Id = 1 and set its name to "feed fish":
{
"id":1,
"name":"feed fish",
"isComplete":true
}
The following image shows the Postman update:
Add a DeleteTodoItem method 2.1
Add the following DeleteTodoItem
method:
// DELETE: api/Todo/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
The DeleteTodoItem
response is 204 (No Content).
Test the DeleteTodoItem method 2.1
Use Postman to delete a to-do item:
- Set the method to
DELETE
. - Set the URI of the object to delete (for example,
https://localhost:5001/api/todo/1
). - Select Send.
The sample app allows you to delete all the items. However, when the last item is deleted, a new one is created by the model class constructor the next time the API is called.
Call the web API with JavaScript 2.1
In this section, an HTML page is added that uses JavaScript to call the web API. jQuery initiates the request. JavaScript updates the page with the details from the web API's response.
Configure the app to serve static files and enable default file mapping by updating Startup.cs with the following highlighted code:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
Create a wwwroot folder in the project directory.
Add an HTML file named index.html to the wwwroot directory. Replace its contents with the following markup:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Add a JavaScript file named site.js to the wwwroot directory. Replace its contents with the following code:
const uri = "api/todo";
let todos = null;
function getCount(data) {
const el = $("#counter");
let name = "to-do";
if (data) {
if (data > 1) {
name = "to-dos";
}
el.text(data + " " + name);
} else {
el.text("No " + name);
}
}
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function(key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function() {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function() {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function(key, item) {
if (item.id === id) {
$("#edit-name").val(item.name);
$("#edit-id").val(item.id);
$("#edit-isComplete")[0].checked = item.isComplete;
}
});
$("#spoiler").css({ display: "block" });
}
$(".my-form").on("submit", function() {
const item = {
name: $("#edit-name").val(),
isComplete: $("#edit-isComplete").is(":checked"),
id: $("#edit-id").val()
};
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$("#spoiler").css({ display: "none" });
}
A change to the ASP.NET Core project's launch settings may be required to test the HTML page locally:
- Open Properties\launchSettings.json.
- Remove the
launchUrl
property to force the app to open at index.html—the project's default file.
This sample calls all of the CRUD methods of the web API. Following are explanations of the calls to the API.
Get a list of to-do items 2.1
jQuery sends an HTTP GET request to the web API, which returns JSON representing an array of to-do items. The success
callback function is invoked if the request succeeds. In the callback, the DOM is updated with the to-do information.
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function(key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function() {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function() {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
Add a to-do item 2.1
jQuery sends an HTTP POST request with the to-do item in the request body. The accepts
and contentType
options are set to application/json
to specify the media type being received and sent. The to-do item is converted to JSON by using JSON.stringify. When the API returns a successful status code, the getData
function is invoked to update the HTML table.
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
Update a to-do item 2.1
Updating a to-do item is similar to adding one. The url
changes to add the unique identifier of the item, and the type
is PUT
.
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
Delete a to-do item 2.1
Deleting a to-do item is accomplished by setting the type
on the AJAX call to DELETE
and specifying the item's unique identifier in the URL.
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
Add authentication support to a web API 2.1
ASP.NET Core Identity adds user interface (UI) login functionality to ASP.NET Core web apps. To secure web APIs and SPAs, use one of the following:
- Azure Active Directory
- Azure Active Directory B2C (Azure AD B2C)
- IdentityServer4
IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. IdentityServer4 enables the following security features:
- Authentication as a Service (AaaS)
- Single sign-on/off (SSO) over multiple application types
- Access control for APIs
- Federation Gateway
For more information, see Welcome to IdentityServer4.
Additional resources 2.1
View or download sample code for this tutorial. See how to download.
For more information, see the following resources:
- Create web APIs with ASP.NET Core
- ASP.NET Core web API documentation with Swagger / OpenAPI
- Razor Pages with Entity Framework Core in ASP.NET Core - Tutorial 1 of 8
- Routing to controller actions in ASP.NET Core
- Controller action return types in ASP.NET Core web API
- Deploy ASP.NET Core apps to Azure App Service
- Host and deploy ASP.NET Core
- YouTube version of this tutorial
- Microsoft Learn: Create a web API with ASP.NET Core