Building services using ASP.NET Core Web API and Visual Studio Code

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:

  • URLs to uniquely identify resources
  • Methods to perform common tasks, such as GET and DELETE
  • The ability to negotiate media formats, such as XML and JSON

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.

Creating an ASP.NET Core Web API project

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:

  • Create a new Web API project
  • Restore dependency packages
  • Start the website
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.

Creating a web service for the Northwind database

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.

Creating the Northwind database

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

Referencing the EF Core NuGet packages

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> 
 

Creating the entity model and database context

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; } 
      } 
    } 

Creating the data repository

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; 
        } 
     } 
   } 

Configuring and registering the data repository

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:

  • Set the Northwind database context to use SQLite and load the connection string from appsettings.json
  • Register the CustomerRepository for use at runtime by ASP.NET Core
        services.AddDbContext<Packt.CS7.Models.Northwind>(options => 
          options.UseSqlite(Configuration 
          .GetConnectionString("NorthwindConnection"))); 
 
        services.AddSingleton<ICustomerRepository, CustomerRepository>(); 

Set the database connection string

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" 
        } 
      } 
    } 

Creating the Web API controller

In the Explorer pane, select the Controllers folder and add a new file named CustomersController.cs.

Note

We could delete the ValuesController.cs file, but it is good to have a simple Web API controller in a service for testing purposes.

In the CustomersController class, add the following code, and note:

  • The controller class registers a route that starts with api and includes the name of the controller, that is, api/customers
  • The constructor uses dependency injection to instantiate the registered repository for the customers
  • There are five methods to perform CRUD operations on customers---two 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 
       } 
     } 
   } 

Note

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.

Testing the web service

In an Integrated Terminal, start the web service by entering the following command:

dotnet run

Testing GET requests with any browser

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:

Testing GET requests with any browser

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:

Testing GET requests with any browser

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:

Testing GET requests with any browser

Testing POST, PUT, DELETE, and other requests with Postman

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:

Testing POST, PUT, DELETE, and other requests with Postman

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.171.51