Although HTTP was originally designed to request and respond with HTML and other resources for us to look at, it is also good to build services. Roy Fielding stated in his doctoral dissertation describing the Representational State Transfer (REST) architectural style that the HTTP standard defines:
GET
and DELETE
To allow the easy creation of services, ASP.NET Core has combined what used to be two types of controller.
In earlier versions of ASP.NET, you would derive from ApiController
to create a Web API service and then register API routes in the same route table that MVC uses.
With ASP.NET Core, you use the same Controller
base class as you used with MVC, except the routes are configured on the controller itself, using attributes, rather than in the route table.
Create a folder named Chapter15
with a subfolder named Ch15_WebApi
.
In Visual Studio Code, open the Ch15_WebApi
folder.
In an Integrated Terminal, enter the following commands to:
dotnet new webapi dotnet restore dotnet run
Start Google Chrome.
Navigate to http:/localhost:5000/api/values
and view the results, as shown in the following output:
["value1","value2"]
Close Google Chrome.
In an Integrated Terminal, press Ctrl + C to stop the console application and shut down the Kestrel web server that is hosting your ASP.NET Core web application.
Unlike ASP.NET Core MVC controllers, ASP.NET Core Web API controllers do not call views to return HTML responses for humans to see in browsers. Instead, they use content negotiation with the client application that made the HTTP request to return XML, JSON, or X-WWW-FORMURLENCODED data formats in the HTTP response.
The client application must then deserialize the data from the negotiated format. The most commonly used format for modern services is JavaScript Object Notation (JSON) because it is compact and works natively with JavaScript in a browser.
Create the Northwind.db
file in the Ch15_WebApiinDebug
etcoreapp1.1
folder by copying the NorthwindSQLite.sql
file into that folder and then entering the following command in Integrated Terminal:
sqlite3 Northwind.db < NorthwindSQLite.sql
Open the Ch15_WebApi.csproj
file and add package references, as shown highlighted in the following code:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore"
Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc"
Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug"
Version="1.1.1" />
<PackageReference Include=
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore"
Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles"
Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design"
Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite"
Version="1.1.1" />
<PackageReference
Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1"
PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
Version="1.1.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference
Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0"
/>
<DotNetCliToolReference
Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools"
Version="1.0.0" />
</ItemGroup>
</Project>
Create a Models
folder in the Ch15_WebApi
folder.
Add two class files to the Models
folder named Northwind.cs
and Customer.cs
.
Northwind.cs
should look like this:
using Microsoft.EntityFrameworkCore; namespace Packt.CS7.Models { public class Northwind : DbContext { public DbSet<Customer> Customers { get; set; } public Northwind(DbContextOptions options) : base(options) {} } }
Customer.cs
should look like this:
using System.ComponentModel.DataAnnotations; namespace Packt.CS7.Models { public class Customer { [Key] [StringLength(5)] public string CustomerID { get; set; } [Required] [StringLength(40)] public string CompanyName { get; set; } [StringLength(30)] public string ContactName { get; set; } [StringLength(15)] public string City { get; set; } [StringLength(15)] public string Country { get; set; } [StringLength(24)] public string Phone { get; set; } } }
Add two class files to the Models
folder named ICustomerRepository.cs
and CustomerRepository.cs
.
ICustomerRepository
should look like this:
using System.Collections.Generic; namespace Packt.CS7.Models { public interface ICustomerRepository { Customer Add(Customer c); IEnumerable<Customer> GetAll(); Customer Find(string id); bool Remove(string id); Customer Update(string id, Customer c); } }
CustomerRepository
should look like this:
using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; namespace Packt.CS7.Models { public class CustomerRepository : ICustomerRepository { // cache the customers in a thread-safe dictionary // so restarting the service will reset the customers // in real world the repository would perform CRUD // on the database private static ConcurrentDictionary<string, Customer> customers; public CustomerRepository(Northwind db) { // load customers from database as a normal // Dictionary with CustomerID is the key, // then convert to a thread-safe // ConcurrentDictionary customers = new ConcurrentDictionary<string, Customer>( db.Customers.ToDictionary(c => c.CustomerID)); } public Customer Add(Customer c) { // normalize CustomerID into uppercase c.CustomerID = c.CustomerID.ToUpper(); // if the customer is new, add it, else // call Update method return customers.AddOrUpdate(c.CustomerID, c, Update); } public IEnumerable<Customer> GetAll() { return customers.Values; } public Customer Find(string id) { id = id.ToUpper(); Customer c; customers.TryGetValue(id, out c); return c; } public bool Remove(string id) { id = id.ToUpper(); Customer c; return customers.TryRemove(id, out c); } public Customer Update(string id, Customer c) { id = id.ToUpper(); c.CustomerID = c.CustomerID.ToUpper(); Customer old; if (customers.TryGetValue(id, out old)) { if (customers.TryUpdate(id, c, old)) { return c; } } return null; } } }
Open the Startup.cs
file.
Import the following namespaces:
using Microsoft.EntityFrameworkCore; using Packt.CS7.Models;
Add the following statements to the bottom of the ConfigureServices
method that will:
appsettings.json
CustomerRepository
for use at runtime by ASP.NET Coreservices.AddDbContext<Packt.CS7.Models.Northwind>(options => options.UseSqlite(Configuration .GetConnectionString("NorthwindConnection"))); services.AddSingleton<ICustomerRepository, CustomerRepository>();
Open the appsettings.json
file and add a connection string named NorthwindConnection
, as shown in the following code:
{
"ConnectionStrings": {
"NorthwindConnection": "Data Source=Northwind.db"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
In the Explorer pane, select the Controllers
folder and add a new file named CustomersController.cs
.
In the CustomersController
class, add the following code, and note:
api
and includes the name of the controller, that is, api/customers
GETs
(all customers or one customer), POST
(create), PUT
(update), and DELETE
:using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using Packt.CS7.Models; namespace Packt.CS7.Controllers { // base address: api/customers [Route("api/[controller]")] public class CustomersController : Controller { private ICustomerRepository repo; // constructor injects registered repository public CustomersController(ICustomerRepository repo) { this.repo = repo; } // GET: api/customers // GET: api/customers/?country=[country] [HttpGet] public IEnumerable<Customer> GetCustomers(string country) { if (string.IsNullOrWhiteSpace(country)) { return repo.GetAll(); } else { return repo.GetAll() .Where(customer => customer.Country == country); } } // GET: api/customers/[id] [HttpGet("{id}", Name = "GetCustomer")] public IActionResult GetCustomer(string id) { Customer c = repo.Find(id); if (c == null) { return NotFound(); // 404 Resource not found } return new ObjectResult(c); // 200 OK } // POST: api/customers // BODY: Customer (JSON, XML) [HttpPost] public IActionResult Create([FromBody] Customer c) { if (c == null) { return BadRequest(); // 400 Bad request } repo.Add(c); return CreatedAtRoute("GetCustomer", new { id = c.CustomerID.ToLower() }, c); // 201 Created } // PUT: api/customers/[id] // BODY: Customer (JSON, XML) [HttpPut("{id}")] public IActionResult Update(string id, [FromBody] Customer c) { id = id.ToUpper(); c.CustomerID = c.CustomerID.ToUpper(); if (c == null || c.CustomerID != id) { return BadRequest(); // 400 Bad request } var existing = repo.Find(id); if (existing == null) { return NotFound(); // 404 Resource not found } repo.Update(id, c); return new NoContentResult(); // 204 No content } // DELETE: api/customers/[id] [HttpDelete("{id}")] public IActionResult Delete(string id) { var existing = repo.Find(id); if (existing == null) { return NotFound(); // 404 Resource not found } repo.Remove(id); return new NoContentResult(); // 204 No content } } }
If you have used older versions of ASP.NET Web API, then you know that in that technology, you could create C# methods that begin with any HTTP method (GET
, POST
, PUT
, and so on), and the controller would automatically execute the correct one. In ASP.NET Core, this doesn't happen anymore because we are not inheriting from ApiController
. So, you must apply an attribute such as [HttpGet]
to explicitly map HTTP methods to C# methods. This allows us to use any name we like for the methods themselves.
In an Integrated Terminal, start the web service by entering the following command:
dotnet run
Start Google Chrome, and in the address bar, enter the following URL:
http://localhost:5000/api/customers
You should see a JSON document returned containing all the 91 customers in the Northwind database, as shown in the following screenshot:
In the address bar, enter the following URL:
http://localhost:5000/api/customers/alfki
You should see a JSON document returned containing only the customer named Alfreds Futterkiste, as shown in the following screenshot:
In the address bar, enter the following URL:
http://localhost:5000/api/customers/?country=USA
You should see a JSON document returned, containing the customers in the USA, as shown in the following screenshot:
There is a free application named Postman that makes it easy to test REST services like the one we just created. Postman is also available as an extension to Google Chrome, although the full application has more features.
In the real world, it would be sensible to test all the methods in our service, for example, the POST
method, using a tool like Postman, as shown in the following screenshot, however the details of doing that are beyond the scope of this book:
To learn more about Postman, visit the following link: https://www.getpostman.com/docs/
In an Integrated Terminal, press Ctrl + C to stop the console application and shut down the Kestrel web server that is hosting your ASP.NET Core web application.
You are now ready to build a mobile app that calls the service.
18.223.171.51