Build Secure Microservices with AWS Lambda and ASP.NET Core

Build Secure Microservices with AWS Lambda and ASP.NET Core

Microservices are fun to build and offer us a scalable path to overcoming problems with tightly coupled dependencies that plague monolithic applications.

Microservices are fun to build and offer us a scalable path to overcoming problems with tightly coupled dependencies that plague monolithic applications.

This post will walk you through building an AWS Lambda microservice written in C# with .NET Core 2.1, and communicating in JSON. We’re bringing together multiple exciting technologies here - microservices, serverless API via AWS Lambda, and authentication using Okta’s easy and convenient identity provider. Each of these technologies is deserving of their own deep-dive article, so there is a lot to cover on these subjects. This post is meant to provide a starting point implementing all three. It will be simple so that anyone can follow along. It will also be kept to a clean, basic implementation that you can expand easily if needed.

Let’s jump right in and build our first microservice - an authentication service.

There are a few things you will need to follow this tutorial.

Visual Studio 2017 If you don’t have it, you can download Visual Studio Community for free. An AWS Account. If you don’t already have one, you can create one here.

Create Your ASP.NET Core Microservice

Create a new AWS Serverless Application (.NET Core) project as shown below. If you do not have this option you need to install the AWS Toolkit for Visual Studio.

You’ll also want to install the Newtonsoft.Json package as shown in the image below.

A microservice architecture is particularly empowering when it comes to scalability. Each component is isolated in its own executable or web service. This means components can run on dedicated hardware, and specific pieces of functionality can be scaled up and down with more granular precision than in an ESB (Enterprise Service Bus). This makes AWS Lambda a complementary medium to host your new microservice. Lambda is a serverless hosting solution that’s highly scalable.

Configure Authentication for Your ASP.NET Microservice

For this example, you’ll need an Okta developer account. If you don’t already have one you can sign up for a free developer account.

You’ll also need an Okta ApiToken. For security reasons, ApiTokens are only displayed once, so make sure you keep it safe once it’s generated. Login to your Okta Developer account, and click API in the main menu, then go to Tokens. On the Tokens screen click Create Token in the top left-hand corner of the page. Give your token a name then click Create Token.

At this point, Okta will generate your ApiToken and display it on the screen. This value will eventually be placed in appSettings.json, but for now, copy it somewhere to keep for later.

Determine the Scope of Your ASP.NET Core Microservice

The utility microservices provide is the loose coupling of dependencies. Everything inside your service is tightly coupled. Everything outside is loosely coupled. Keep this in mind while trying to determine how big your microservice should be. You don’t want your service to grow to the point where your writing class libraries that are tightly-coupled dependencies of more than one “micro-service”.

Following this rule, a microservice could offer a very deep pool of functionality, that can always be made deeper but should very rarely (if ever) be made wider.

Create the Objects for Your ASP.NET Core Microservice

In your project’s root folder, create a folder called Model. Inside that file, you’ll need to create the following model objects. It’s easiest to create all these first so that the objects for transferring data will already be in place when we start writing our logic.

OktaSettings

The OktaSettings object will be used to store your Okta domain and the API token that enables programmatic access to your Okta account. Notice that these members - Domain and ApiToken - match the attributes in our appSettings.json file under the Okta heading. When the program is initiated, the config file will be read and the values will be stored in memory, where they will then be accessible via IOptions<OktaSettings>.

namespace AuthenticationService.Model
{
    public class OktaSettings
    {
        public string Domain { get; set; }
        public string ApiToken { get; set; }
    }
}


OktaAuthenticationRequest

This class is used to send data to the Okta Authentication API at <span

class="okta-preview-domain"><span

class="okta-preview-domain">[https://{yourOktaDomain}</span></span>/api/v1/authn](https://{yourOktaDomain}/api/v1/authn "https://{yourOktaDomain}</span></span>/api/v1/authn"). An instance of this class will be passed to Okta with the username and password supplied to our authentication service. This is the call that tells us whether the login was successful.

For more information on Okta’s authentication API, including postman examples of HTTP requests, click here.

namespace AuthenticationService.Model
{
    public class OktaAuthenticationRequest
    {
        public string username { get; set; }
        public string password { get; set; }
    }
}


Credentials

Our AuthenticationController will expose a single action - Authenticate. That method accepts a single input - Credentials. Credentials is used to pass the username and password in need of authentication to our microservice.

namespace AuthenticationService.Model
{
    public class Credentials
    {
        public string PublicKey { get; set; }
        public string PrivateKey { get; set; }
    }
}


Keeping your microservice small, with only a single controller and a single action, has beneficial consequences downstream on systems dependent upon your new microservice. On a microservice driven platform, disasters are isolated. If one service goes down, the rest of the services and the unrelated features of the application will still be operational. The impacted portion of the app can display a friendly message informing the user of the outage while your team sets to work fixing the production issue. If the service logic and app logic were tightly coupled, the entire app would be brought down with the service.

AuthenticationResponse

Just like Credentials is used to pass data to the Authenticate action, AuthenticationResponse is used to return data from the Authenticate action. It will tell the client calling your microservice if the call was successful or it will return an error message.

In the event of success, you will also define the User and Session members.

using System;

namespace AuthenticationService.Model
{
    public class AuthenticationResponse
    {
        public bool WasSuccessful { get; set; }
        public string Message { get; set; }

        public AuthenticatedSession Session { get; set; }
        public AuthenticatedUser User { get; set; }
    }
}


AuthenticatedSession

The AuthenticatedSession class is made to encapsulate details about the user’s session if they are successfully authenticated. This will be used to return that data to the client calling your microservice.

using System;

namespace AuthenticationService.Model
{
    public class AuthenticatedSession
    {
        public string Token { get; set; }
        public string ExpiresAt { get; set; }
    }
}


AuthenticatedUser

Much like AuthenticatedSession, AuthenticatedUser is made to encapsulate details about the user, if they are successfully authenticated. This will be used to return that data to the client calling our microservice.

using System;

namespace AuthenticationService.Model
{
    public class AuthenticatedUser
    {
        public string Id { get; set; }
        public DateTime PasswordChanged { get; set; }
        public string Login { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Locale { get; set; }
        public string TimeZone { get; set; }
    }
}


Load the Configuration for the ASP.NET Core Microservice

At startup, we’ll want to process the config file so that we can get access to the configured Okta Domain, and ApiToken. To do this add the following lines to the bottom of the ConfigureServices method of your Startup.cs file. This tells dot net core where the config details are, and gives it our OktaSettings class, to be used for accessing the configuration settings.

// Okta configuration
services.Configure<OktaSettings>(Configuration.GetSection("Okta"));
services.AddOptions();


Add the Authentication Steps to Your ASP.NET Core Microservice

The next step is to implement authentication by calling Okta’s Authentication API. Add a file called AuthenticationController.cs to your Controllers folder. Then paste the following code:

using System;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Text;

using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Mvc;

using AuthenticationService.Model;

namespace AuthenticationService.Controllers
{
    [Route("api/[controller]/[action]")]
    public class AuthenticationController : Controller
    {
        private IOptions<OktaSettings> settings;
        public AuthenticationController(IOptions<OktaSettings> settings)
        {
            this.settings = settings;
        }

        [HttpPost]
        public async Task<AuthenticationResponse> Authenticate(Credentials input)
        {
            var ret = new AuthenticationResponse();

            AuthenticatedSession session = null;
            AuthenticatedUser user = null;

            //generate URL for service call using your configured Okta Domain
            string url = string.Format("{0}/api/v1/authn", this.settings.Value.Domain);

            //build the package we're going to send to Okta
            var data = new OktaAuthenticationRequest() { username = input.PublicKey, password = input.PrivateKey };

            //serialize input as json
            var json = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");

            //create HttpClient to communicate with Okta's web service
            using (HttpClient client = new HttpClient())
            {
                //Set the API key
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SSWS", this.settings.Value.ApiToken);

                //Post the json data to Okta's web service
                using (HttpResponseMessage res = await client.PostAsync(url, json))

                //Get the response from the server
                using (HttpContent content = res.Content)
                {
                    //get json string from the response
                    var responseJson = await content.ReadAsStringAsync();
                    Console.WriteLine(responseJson);

                    //deserialize json into complex object
                    dynamic responseObj = JsonConvert.DeserializeObject(responseJson);

                    //determine if the returned status is success
                    if (responseObj.status == "SUCCESS")
                    {
                        //get session data
                        session = new AuthenticatedSession()
                        {
                            Token = responseObj.sessionToken,
                            ExpiresAt = responseObj.expiresAt
                        };

                        //get user data
                        user = new AuthenticatedUser()
                        {
                            Id = responseObj._embedded.user.id,
                            Login = responseObj._embedded.user.login,
                            Locale = responseObj._embedded.user.locale,
                            TimeZone = responseObj._embedded.user.timeZone,
                            FirstName = responseObj._embedded.user.firstName,
                            LastName = responseObj._embedded.user.lastName
                        };
                    }
                }
            }

            //return results of the operation packaged in a AuthenticationResponse object
            var wasSuccess = session != null && user != null;
            return new AuthenticationResponse()
            {
                WasSuccessful = wasSuccess,
                Message = wasSuccess ? "Success" : "Invalid username and password",

                Session = session,
                User = user
            };
        }
    }
}


You may notice that the constructor of the class accepts an instance of IOptions<OktaSettings>. OktaSettings is the same class you defined above in your Model folder to store your Okta configuration settings.

The configuration code you added to Startup.cs enables IOptions<OktaSettings> to be recognized by .Net Core and the framework will automatically supply the input when the class is initialized. This gives you access to the Okta configuration settings in appSettings.json.

The Authenticate action has been well commented to help you understand what each line is doing. Here are the high-level steps that the action goes through:

Creates an instance of OktaAuthenticationRequest to pass the supplied username and password to Okta’s authentication service JSON encodes the data Creates an instance of HttpClient to communicate with Okta, and sets the authentication header value using the ApiToken Posts the JSON encoded data to Okta and awaits a response Deserialized JSON response stream, and evaluates if "SUCCESS" was the returned status If the login was unsuccessful it returns false for WasSuccess and "Invalid username and password" for Message. If it was a success it returns true for WasSuccess, and returns the session and user data provided by Okta

Add Configuration Management to Your Microservice

All your configuration for the service can be found in the appSettings.json file. Following best practice, you’ll want to configure each environment to point at a different instance of Okta. This isolates each environment so tests in one environment don’t interfere with tests in another. It helps you become familiar with your Okta settings so that you can be deliberate when configuring Okta and your software. It presents an opportunity to rehearse deployment and configuration. All of these things are useful steps you don’t want to skip when working your solution to production.

You’ll have to populate the below settings from your Okta account.

  "Okta": {
    "Domain": "https://{yourOktaDomain}",
    "ApiToken": "{yourApiToken}"
  }


Deploy Your Lambda Microservice

Deploying your new microservice as a Lambda function is easy. The AWS Toolkit for Visual Studio will create the cloud components for you. To deploy your microservice, in the Solution Explorer, right click on your web service project, and select Publish to AWS Lambda.

Click on the little person icon next to Account Profile to Use. In the resulting pop-up window, you can use “default” as the profile name. You’ll need to enter Access Key information. To create an Access Key in AWS, click on your account name in the top right of your AWS dashboard and choose My Security Credentials. Expand the Access Keys section and click Create New Access Key. In the pop-up window, there will be a link to show the Access Key ID and Secret Access Key. You can enter them in the deployment window in Visual Studio, or download the keyfile and click Import from csv file… to import it.

Then, you’ll need to get Provide a Stack Name. This can be whatever you want to name it and will be used by the wizard when creating your AWS resource to prefix automatically generated component names. You can also provide the S3 Bucket you’d like the code deployed to. You can click the New… button to create an S3 Bucket to use. Click Publish to start the deployment.

If your cloud resources have not already been created, publish will create them for you. During publish, the modal window will disappear and a deployment tab will appear in your IDE. Once the status shown in the tab displays UPDATE_COMPLETE, your deployment has finished.

Copy the AWS Serverless URL and use it to test your microservice. Your URL may look something like mine shown below: [https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod](https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod "https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod")

This means the URL to our authenticate action looks like: [https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod/api/authentication/authenticate](https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod/api/authentication/authenticate "https://25ayfn7ecl.execute-api.us-east-2.amazonaws.com/Prod/api/authentication/authenticate")

Open Postman and POST to the above URL, while sending the following form data:

  • PublicKey = {YOURUSERNAME}
  • PrivateKey = {YOURPASSWORD}

If you get a response back like the following, you’re good to go!

{
    "wasSuccessful": true,
    "message": "Success",
    "session": {
        "token": "2011129scHesxiG6Psr1ZCtdADBEJ1iBMr_KtTnaeSV4ZJvpP03BOP_",
        "expiresAt": "03/19/2019 18:32:20"
    },
    "user": {
        "id": "00uitftb3egstVhnQ0h7",
        "passwordChanged": "0001-01-01T00:00:00",
        "login": null,
        "firstName": null,
        "lastName": null,
        "locale": null,
        "timeZone": null
    }
}


ASP.NET Core Microservice Next Steps and Considerations for Production

If you’re going to take this to production you’re definitely going to want to have an SSL certificate. Select a certificate authority like AWS or GoDaddy, and check out their SSL offerings. Purchase your SSL and install it on your web server or AWS account.

Authentication is about verifying the identity of the client. Authorization, on the other hand, is what you sometimes do after you know who you’re dealing with and you want to know what they have permission to access. If your authorization logic is simple, it may be appropriate to add another controller and action to this existing microservice. If your logic is more complex, then it should be put into a new microservice. That new microservice could then be passed the session token (returned from the authentication microservice we built today), and the identity of a digital resource, and it will return the level of access to that resource.

Create Login and Registration in Your ASP.NET Core MVC App

Create Login and Registration in Your ASP.NET Core MVC App

This tutorial walks you through setting up login and registration with ASP.NET Core MVC. In this tutorial, you learned how to add authentication to your ASP.NET Core MVC app and allow users to register for a new account.

This tutorial walks you through setting up login and registration with ASP.NET Core MVC. In this tutorial, you learned how to add authentication to your ASP.NET Core MVC app and allow users to register for a new account.

User authentication and authorization are common features in web applications, but building these mechanics has the potential to take a lot of time. Doing so requires setting up persistent storage for user information (in some type of database) and paying keen attention to potential security issues around sensitive operations like hashing passwords, password reset workflows, etc. - weeks of development time begin to add up before we ever get to the functionality that delivers value to your users.

In this post, we’ll walk through how Okta simplifies this process for us and set up a simple integration for an ASP.NET Core MVC app using the Okta NuGet package. We’ll build functionality for users to register for new accounts and login with their Okta credentials.

Scaffold Your ASP.NET Project

To follow along with this tutorial start by creating a new app in the console:

mkdir MyOktaProject
cd MyOktaProject
dotnet new mvc


Configure User Registration

If you don’t already have one, you’ll need to create an Okta developer account. Doing so will give you a URL for your organization called an “Okta domain”. It will also allow you to create your login credentials for accessing the Okta dashboard.

Upon submission of this form, you will receive an email Okta with instructions to obtain your credentials and complete your registration.

Execute the following steps to configure Okta so that users can register themselves for an account.

  1. From the Administrative Dashboard, hover over Users and click Registration
  2. Click Enable Registration
  3. Save the changes

Configure Basic User Authentication

Once you have access to your account you can proceed to the Dashboard using a link like the one below:

<span

class="okta-preview-domain">https://{yourOktaDomain}/admin/dashboard

On the Dashboard, click Applications in the main menu and on the Application screen, click Add Application.

Select Web then click Next.

On the Create New Application screen, set the following values:

Click Done, then click Edit next to General Settings on your newly created Okta app. Edit the following values:

Logout redirect URIs: https://localhost:5001/signout-callback-oidc

Initiate login URI: https://localhost:5001/authorization-code/callback

Add .NET Authentication Dependencies

Once your account is set up you need to add the Okta.Sdk library to your project. This post will take the approach of using the NuGet package, but the Github repository for Okta.AspNetCore can be found here.

To proceed simply search for the latest version of the Okta.Sdk NuGet package in your IDE of choice (version 1.2.0 at the time of this publication) and install it. If you’re using Visual Studio you can do this by right-clicking on the project in the solution explorer and selecting Manage NuGet Packages. For those of you not using Visual Studio, add the package via console window using the following command:

dotnet add package Okta.Sdk --version 1.2.0


Configure Your ASP.NET App for Login

Authentication works by redirecting users to the Okta website, where they will log in with their credentials, and then be returned to your site via the URL you configured above.

Add the following code to your appsettings.json file:

  "Okta": {
    "Issuer": "https://{yourOktaDomain}/oauth2/default",
    "ClientId": "{yourClientId}",
    "ClientSecret": "{yourClientSecret}"
  }


You can find each of the actual values needed to replace the settings in the config above in the following places:

ClientId refers to the client ID of the Okta application ClientSecret refers to the client secret of the Okta application Issuer will need the text {yourOktaDomain} replaced with your Okta domain, found at the top-right of the Dashboard page

Add some using statements to your Startup.cs file:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;


Add the following code to the top of the ConfigureServices method in your Startup.cs file:

services.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.ClientId = Configuration["okta:ClientId"];
        options.ClientSecret = Configuration["okta:ClientSecret"];
        options.Authority = Configuration["okta:Issuer"];
        options.CallbackPath = "/authorization-code/callback";
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.UseTokenLifetime = false;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name"
        };
    });


In the Configure() method of your Startup.cs file add this line just before the app.UseMvc() method:

app.UseAuthentication();


Add the following MeViewModel to the Models directory:

using System.Collections.Generic;

namespace OktaAspNetCoreMvc.Models
{
    public class MeViewModel
    {
        public string Username { get; set; }

        public bool SdkAvailable { get; set; }

        public dynamic UserInfo { get; set; }

        public IEnumerable<string> Groups { get; set; }
    }
}


Add Login to Your ASP.NET App

Now that all the configuration and plumbing is done, you’re ready to add the code that will actually log users into your application.

Add the following AccountController to the Controller directory.

The controller exposes the Login() action. If the user has already been authenticated, the Login() action will redirect them to the home page. Otherwise, it will redirect them to the Okta login screen.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Okta.Sdk;

namespace OktaAspNetCoreMvc.Controllers
{
    public class AccountController : Controller
    {
        private readonly IOktaClient _oktaClient;

        public AccountController(IOktaClient oktaClient = null)
        {
            _oktaClient = oktaClient;
        }

        public IActionResult Login()
        {
            if (!HttpContext.User.Identity.IsAuthenticated)
            {
                return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
            }

            return RedirectToAction("Index", "Home");
        }
    }
}


Add the following code to your _Layout.cshtml file, just below the main menu to add the login button, or a welcome message, based on the current user’s status.:

   @if (User.Identity.IsAuthenticated)
    {
        <ul class="nav navbar-nav navbar-right">
            <li><p class="navbar-text">Hello, @User.Identity.Name</p></li>
        </ul>
    }
    else
    {
        <ul class="nav navbar-nav navbar-right">
            <li><a asp-controller="Account" asp-action="Login">Log in</a></li>
        </ul>
    }


For information on user authorization using Okta groups check out Lee Brandt’s article on user authorization in ASP.NET Core with Okta.

Register Users

If you following the instructions above to enable self-service registration the “Don’t have an account? Sign Up” message will appear at the bottom of the login form. In the next step, you’ll run the application.

Log In Using ASP.NET

That’s it! To run your solution open up a terminal and enter the following command:

dotnet run


Then navigate to http://localhost:5001 in your browser and enjoy!

The source code for this tutorial is available on GitHub.

Now you have a website with a working login and user registration form. Your website also allows users to recover lost passwords. By repeating these steps you can create a network of tools that your users can access all with the same login.

Learn More

The Complete ASP.NET MVC 5 Course

Build a Real-world App with ASP.NET Core and Angular 2 (4+)

ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course

Rest Api’s in Asp.Net and C#

Hands on ASP .Net Core 2

AWS Certified Solution Architect Associate

AWS Certified Solution Architect Associate

This course will help you in the final preparation steps for your AWS Certified Solution Architect Associate - Certification Exam.

In this course , we will go through the different concepts that get asked in the exam and map them to the different domain objectives for the exam.

This is good revision guide before you attempt the certification exam

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about AWS

A Complete Guide on Deploying a Node app to AWS with Docker

AWS Certified Solutions Architect - Associate 2019

AWS Lambda vs. Azure Functions vs. Google Functions

AWS Certified Developer - Associate 2019

Create and Deploy AWS and AWS Lambda using Serverless Framework

Introduction To AWS Lambda

Why Azure is Better Than AWS

How to build RESTful APIs with ASP.NET Core

How to build RESTful APIs with ASP.NET Core

A step by step guide on how to implement clean, maintainable RESTful APIs

A step by step guide on how to implement clean, maintainable RESTful APIs

Overview

RESTful is not a new term. It refers to an architectural style where web services receive and send data from and to client apps. The goal of these applications is to centralize data that different client apps will use.

Choosing the right tools to write RESTful services is crucial since we need to care about scalability, maintenance, documentation, and all other relevant aspects. The ASP.NET Core gives us a powerful, easy to use API that is great to achieve these goals.

In this article, I’ll show you how to write a well structured RESTful API for an “almost” real world scenario, using the ASP.NET Core framework. I’m going to detail common patterns and strategies to simplify the development process.

I’ll also show you how to integrate common frameworks and libraries, such as Entity Framework Core and AutoMapper, to deliver the necessary functionalities.

Prerequisites

I expect you to have knowledge of object-oriented programming concepts.

Even though I’m going to cover many details of the C# programming language, I recommend you to have basic knowledge of this subject.

I also assume you know what REST is, how the HTTP protocol works, what are API endpoints and what is JSON. Here is a great introductory tutorial on this subject. The final requirement is that you understand how relational databases work.

To code along with me, you will have to install the .NET Core 2.2, as well as Postman, the tool I’m going to use to test the API. I recommend you to use a code editor such as Visual Studio Code to develop the API. Choose the code editor you prefer. If you choose this code editor, I recommend you to install the C# extension to have better code highlighting.

You can find a link to the Github repository of the API at the end of this article, to check the final result.

The Scope

Let’s write a fictional web API for a supermarket. Let’s imagine we have to implement the following scope:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

To simplify the example, I won’t handle products in stock, product shipping, security and any other functionality. The given scope is enough to show you how ASP.NET Core works.

To develop this service, we basically need two API endpoints: one to manage categories and one to manage products. In terms of JSON communication, we can think of responses as follow:

API endpoint: /api/categories

JSON Response (for GET requests):

{

  [

    { "id": 1, "name": "Fruits and Vegetables" },

    { "id": 2, "name": "Breads" },

    … // Other categories

  ]

}

API endpoint: /api/products

JSON Response (for GET requests):

{

  [

    {

      “id”: 1,

      “name”: “Sugar”,

      “quantityInPackage”: 1,

      “unitOfMeasurement”: “KG”

      “category”: {

        “id”: 3,

        “name”: “Sugar”

      }

    },

    … // Other products

  ]

}

Let’s get started writing the application.

Step 1 — Creating the API

First of all, we have to create the folders structure for the web service, and then we have to use the .NET CLI tools to scaffold a basic web API. Open the terminal or command prompt (it depends on the operating system you are using) and type the following commands, in sequence:

mkdir src/Supermarket.API

cd src/Supermarket.API

dotnet new webapi

The first two commands simply create a new directory for the API and change the current location to the new folder. The last one generates a new project following the Web API template, that is the kind of application we’re developing. You can read more about these command and other project templates you can generate checking this link.

The new directory now will have the following structure:

Structure Overview

An ASP.NET Core application consists of a group of middlewares (small pieces of the application attached to the application pipeline, that handle requests and responses) configured in the Startup class. If you’ve already worked with frameworks like Express.js before, this concept isn’t new to you.

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.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();
    }
}

When the application starts, the Mainmethod, from the Program class, is called. It creates a default web host using the startup configuration, exposing the application via HTTP through a specific port (by default, port 5000 for HTTP and 5001 for HTTPS).

namespace Supermarket.API
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

Take a look at the ValuesController class inside the Controllers folder. It exposes methods that will be called when the API receives requests through the route /api/values.

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }

    // POST api/values
    [HttpPost]
    public void Post([FromBody] string value)
    { 
    }

    // PUT api/values/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody] string value)
    {   
    }

    // DELETE api/values/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {  
    }
}

Don’t worry if you don’t understand some part of this code. I’m going to detail each one when developing the necessary API endpoints. For now, simply delete this class, since we’re not going to use it.

Step 2 — Creating the Domain Models

I’m going to apply some design concepts that will keep the application simple and easy to maintain.

Writing code that can be understood and maintained by yourself is not this difficult, but you have to keep in mind that you’ll work as part of a team. If you don’t take care on how you write your code, the result will be a monster that will give you and your teammates constant headaches. It sounds extreme, right? But believe me, that’s the truth.

Let’s start by writing the domain layer. This layer will have our models classes, the classes that will represent our products and categories, as well as repositories and services interfaces. I’ll explain these last two concepts in a while.

Inside the Supermarket.API directory, create a new folder called Domain. Within the new domain folder, create another one called Models. The first model we have to add to this folder is the Category. Initially, it will be a simple Plain Old CLR Object (POCO) class. It means the class will have only properties to describe its basic information.

using System.Collections.Generic;

namespace Supermarket.API.Domain.Models
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public IList<Product> Products { get; set; } = new List<Product>();
    }
}

The class has an Idproperty, to identify the category, and a Nameproperty. We also have a Productsproperty. This last one will be used by Entity Framework Core, the ORM most ASP.NET Core applications use to persist data into a database, to map the relationship between categories and products. It also makes sense thinking in terms of object-oriented programming, since a category has many related products.

We also have to create the product model. At the same folder, add a new Product class.

namespace Supermarket.API.Domain.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public short QuantityInPackage { get; set; }
        public EUnitOfMeasurement UnitOfMeasurement { get; set; }

        public int CategoryId { get; set; }
        public Category Category { get; set; }
    }
}

The product also has properties for the Id and name. The is also a property QuantityInPackage, that tells how many units of the product we have in one pack (remember the biscuits example of the application scope) and a UnitOfMeasurementproperty. This one is represented by an enum type, that represents an enumeration of possible units of measurement. The last two properties, CategoryIdand Category will be used by the ORM to map the relationship between products and categories. It indicates that a product has one, and only one, category.

Let’s define the last part of our domain models, the EUnitOfMeasurementenum. By convention, enums in C# should start with an “E” in front of their names.

using System.ComponentModel;

namespace Supermarket.API.Domain.Models
{
    public enum EUnitOfMeasurement : byte
    {
        [Description("UN")]
        Unity = 1,

        [Description("MG")]
        Milligram = 2,

        [Description("G")]
        Gram = 3,

        [Description("KG")]
        Kilogram = 4,

        [Description("L")]
        Liter = 5
    }
}

The code is really straightforward. Here we defined only a handful of possibilities for units of measurement, however, in a real supermarket system, you may have many other units of measurement, and maybe a separate model for that.

Notice the Descriptionattribute applied over every enumeration possibility. An attribute is a way to define metadata over classes, interfaces, properties and other components of the C# language. In this case, we’ll use it to simplify the responses of the products API endpoint, but you don’t have to care about it for now. We’ll come back here later.

Our basic models are ready to be used. Now we can start writing the API endpoint that is going to manage all categories.

Step 3 — The Categories API

In the Controllers folder, add a new class called CategoriesController.

By convention, all classes in this folder that end with the suffix “Controller” will become controllers of our application. It means they are going to handle requests and responses. You have to inherit this class from the Controller class, defined in the namespace Microsoft.AspNetCore.Mvc.

A namespace consists of a group of related classes, interfaces, enums, and structs. You can think of it as something similar to modules of the Javascript language, or packages from Java.

The new controller should respond through the route /api/categories. We achieve this by adding the Routeattribute above the class name, specifying a placeholder that indicates that the route should use the class name without the controller suffix, by convention.

using Microsoft.AspNetCore.Mvc;

namespace Supermarket.API.Controllers
{
    [Route("/api/[controller]")]
    public class CategoriesController : Controller
    {
    }
}

Let’s start handling GET requests. First of all, when someone requests data from /api/categories via GET verb, the API needs to return all categories. We can create a category service for this purpose.

Conceptually, a service is basically a class or interface that defines methods to handle some business logic. It is a common practice in many different programming languages to create services to handle business logic, such as authentication and authorization, payments, complex data flows, caching and tasks that require some interaction between other services or models.

Using services, we can isolate the request and response handling from the real logic needed to complete tasks.

The service we’re going to create initially will define a single behavior**,** or method: a listing method. We expect that this method returns all existing categories in the database.

For simplicity, we won’t deal with data pagination or filtering in this case. I’ll write an article in the future showing how to easily handle these features.

To define an expected behavior for something in C# (and in other object-oriented languages, such as Java, for example), we define an interface. An interface tells how something should work, but does not implement the real logic for the behavior. The logic is implemented in classes that implement the interface. If this concept isn’t clear for you, don’t worry. You’ll understand it in a while.

Within the Domain folder, create a new directory called Services. There, add an interface called ICategoryService. By convention, all interfaces should start with the capital letter_“I”_ in C#. Define the interface code as follows:

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;

namespace Supermarket.API.Domain.Services
{
    public interface ICategoryService
    {
         Task<IEnumerable<Category>> ListAsync();
    }
}

The implementations of the ListAsync method must asynchronously return an enumeration of categories.

The Task class, encapsulating the return, indicates asynchrony. We need to think in an asynchronous method due to the fact that we have to wait for the database to complete some operation to return the data, and this process can take a while. Notice also the “async” suffix. It’s a convention that indicates that our method should be executed asynchronously.

We have a lot of conventions, right? I personally like it, because it keeps applications easy to read, even if you’re new to a company that uses .NET technology.

“ - Ok, we defined this interface, but it does nothing. How can it be useful?”

If you come from a language such as Javascript or another non-strongly typed language, this concept may seem strange.

Interfaces allow us to abstract the desired behavior from the real implementation. Using a mechanism known as dependency injection, we can implement these interfaces and isolate them from other components.

Basically, when you use dependency injection, you define some behaviors using an interface. Then, you create a class that implements the interface. Finally, you bind the references from the interface to the class you created.

“ - It sounds really confusing. Can’t we simply create a class that does these things for us?”

Let’s continue implementing our API and you will understand why to use this approach.

Change the CategoriesController code as follows:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Services;

namespace Supermarket.API.Controllers
{
    [Route("/api/[controller]")]
    public class CategoriesController : Controller
    {
        private readonly ICategoryService _categoryService;
        
        public CategoriesController(ICategoryService categoryService)
        {
            this._categoryService = categoryService;   
        }

        [HttpGet]
        public async Task<IEnumerable<Category>> GetAllAsync()
        {
            var categories = await _categoryService.ListAsync();
            return categories;
        }
    }
}

I have defined a constructor function for our controller (a constructor is called when a new instance of a class is created), and it receives an instance of ICategoryService. It means the instance can be anything that implements the service interface. I store this instance in a private, read-only field _categoryService. We’ll use this field to access the methods of our category service implementation.

By the way, the underscore prefix is another common convention to denote a field. This convention, in special, is not recommended by the official naming convention guideline of .NET, but it is a very common practice as a way to avoid having to use the “this” keyword to distinguish class fields from local variables. I personally think it’s much cleaner to read, and a lot of frameworks and libraries use this convention.

Below the constructor, I defined the method that is going to handle requests for /api/categories. The HttpGetattribute tells the ASP.NET Core pipeline to use it to handle GET requests (this attribute can be omitted, but it’s better to write it for easier legibility).

The method uses our category service instance to list all categories and then returns the categories to the client. The framework pipeline handles the serialization of data to a JSON object. The IEnumerable<Category>type tells the framework that we want to return an enumeration of categories, and the Task type, preceded by the async keyword, tells the pipeline that this method should be executed asynchronously. Finally, when we define an async method, we have to use the await keyword for tasks that can take a while.

Ok, we defined the initial structure of our API. Now, it is necessary to really implement the categories service.

Step 4 — Implementing the Categories Service

In the root folder of the API (the Supermarket.API folder), create a new one called Services. Here we’ll put all services implementations. Inside the new folder, add a new class called CategoryService. Change the code as follows:

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Services;

namespace Supermarket.API.Services
{
    public class CategoryService : ICategoryService
    {
        public async Task<IEnumerable<Category>> ListAsync()
        {
        }
    }
}

It’s simply the basic code for the interface implementation, but we still don’t handle any logic. Let’s think in how the listing method should work.

We need to access the database and return all categories, then we need to return this data to the client.

A service class is not a class that should handle data access. There is a pattern called Repository Pattern that is used to manage data from databases.

When using the Repository Pattern, we define repository classes, that basically encapsulate all logic to handle data access. These repositories expose methods to list, create, edit and delete objects of a given model, the same way you can manipulate collections. Internally, these methods talk to the database to perform CRUD operations, isolating the database access from the rest of the application.

Our service needs to talk to a category repository, to get the list of objects.

Conceptually, a service can “talk” to one or more repositories or other services to perform operations.

It may seem redundant to create a new definition for handling the data access logic, but you will see in a while that isolating this logic from the service class is really advantageous.

Let’s create a repository that will be responsible for intermediating the database communication as a way to persist categories.

Step 5 — The Categories Repository and the Persistence Layer

Inside the Domain folder, create a new directory called Repositories. Then, add a new interface called ICategoryRespository. Define the interface as follow:

https://gist.github.com/evgomes/a9263b0e596e02509e783c32f9fc5469.js

The initial code is basically identical to the code of the service interface.

Having defined the interface, we can come back to the service class and finish implementing the listing method, using an instance of ICategoryRepository to return the data.

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Domain.Services;

namespace Supermarket.API.Services
{
    public class CategoryService : ICategoryService
    {
        private readonly ICategoryRepository _categoryRepository;

        public CategoryService(ICategoryRepository categoryRepository)
        {
            this._categoryRepository = categoryRepository;
        }

        public async Task<IEnumerable<Category>> ListAsync()
        { 
            return await _categoryRepository.ListAsync();
        }
    }
}

Now we have to implement the real logic of the category repository. Before doing it, we have to think about how we are going to access the database.

By the way, we still don’t have a database!

We’ll use the Entity Framework Core (I’ll call it EF Core for simplicity) as our database ORM. This framework comes with ASP.NET Core as its default ORM and exposes a friendly API that allows us to map classes of our applications to database tables.

The EF Core also allows us to design our application first, and then generate a database according to what we defined in our code. This technique is called code first. We’ll use the code first approach to generate a database (in this example, in fact, I’m going to use an in-memory database, but you will be able to easily change it to a SQL Server or MySQL server instance, for example).

In the root folder of the API, create a new directory called Persistence. This directory is going to have everything we need to access the database, such as repositories implementations.

Inside the new folder, create a new directory called Contexts, and then add a new class called AppDbContext. This class must inherit DbContext, a class EF Core uses to map your models to database tables. Change the code in the following way:

using Microsoft.EntityFrameworkCore;

namespace Supermarket.API.Domain.Persistence.Contexts
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }
    }
}

The constructor we added to this class is responsible for passing the database configuration to the base class through dependency injection. You’ll see in a moment how this works.

Now, we have to create two DbSet properties. These properties aresets (collections of unique objects) that map models to database tables.

Also, we have to map the models’ properties to the respective table columns, specifying which properties are primary keys, which are foreign keys, the column types, etc. We can do this overriding the method OnModelCreating, using a feature called Fluent API to specify the database mapping. Change the AppDbContext class as follows:

using Microsoft.EntityFrameworkCore;
using Supermarket.API.Domain.Models;

namespace Supermarket.API.Persistence.Contexts
{
    public class AppDbContext : DbContext
    {
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }

        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            
            builder.Entity<Category>().ToTable("Categories");
            builder.Entity<Category>().HasKey(p => p.Id);
            builder.Entity<Category>().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd();
            builder.Entity<Category>().Property(p => p.Name).IsRequired().HasMaxLength(30);
            builder.Entity<Category>().HasMany(p => p.Products).WithOne(p => p.Category).HasForeignKey(p => p.CategoryId);

            builder.Entity<Category>().HasData
            (
                new Category { Id = 100, Name = "Fruits and Vegetables" }, // Id set manually due to in-memory provider
                new Category { Id = 101, Name = "Dairy" }
            );

            builder.Entity<Product>().ToTable("Products");
            builder.Entity<Product>().HasKey(p => p.Id);
            builder.Entity<Product>().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd();
            builder.Entity<Product>().Property(p => p.Name).IsRequired().HasMaxLength(50);
            builder.Entity<Product>().Property(p => p.QuantityInPackage).IsRequired();
            builder.Entity<Product>().Property(p => p.UnitOfMeasurement).IsRequired();
        }
    }
}

The code is intuitive.

We specify to which tables our models should be mapped. Also, we set the primary keys, using the method HasKey, the table columns, using the Property method, and some constraints such as IsRequired, HasMaxLength, and ValueGeneratedOnAdd, everything with lambda expressions in a “fluent way” (chaining methods).

Take a look at the following piece of code:

builder.Entity<Category>()
       .HasMany(p => p.Products)
       .WithOne(p => p.Category)
       .HasForeignKey(p => p.CategoryId);

Here we’re specifying a relationship between tables. We say that a category has many products, and we set the properties that will map this relationship (Products, from Category class, and Category, from Product class). We also set the foreign key (CategoryId).

Take a look at this tutorial if you want to learn how to configure one-to-one and many-to-many relationships using EF Core, as well as how to use it as a whole.

There is also a configuration for seeding data, through the method HasData:

builder.Entity<Category>().HasData

(

  new Category { Id = 100, Name = “Fruits and Vegetables” },

  new Category { Id = 101, Name = “Dairy” }

);

Here we simply add two example categories by default. That’s necessary to test our API endpoint after we finish it.

Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.> Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.
Having implemented the database context class, we can implement the categories repository. Add a new folder called Repositories inside the Persistence folder, and then add a new class called BaseRepository.

using Supermarket.API.Persistence.Contexts;

namespace Supermarket.API.Persistence.Repositories
{
    public abstract class BaseRepository
    {
        protected readonly AppDbContext _context;

        public BaseRepository(AppDbContext context)
        {
            _context = context;
        }
    }
}

This class is just an abstract class that all our repositories will inherit. An abstract class is a class that don’t have direct instances. You have to create direct classes to create the instances.

The BaseRepository receives an instance of our AppDbContext through dependency injection and exposes a protected property (a property that can only be accessible by the children classes) called _context, that gives access to all methods we need to handle database operations.

Add a new class on the same folder called CategoryRepository. Now we’ll really implement the repository logic:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Persistence.Contexts;

namespace Supermarket.API.Persistence.Repositories
{
    public class CategoryRepository : BaseRepository, ICategoryRepository
    {
        public CategoryRepository(AppDbContext context) : base(context)
        {
        }

        public async Task<IEnumerable<Category>> ListAsync()
        {
            return await _context.Categories.ToListAsync();
        }
    }
}

The repository inherits the BaseRepository and implements ICategoryRepository.

Notice how simple it is to implement the listing method. We use the Categories database set to access the categories table and then call the extension method ToListAsync, which is responsible for transforming the result of a query into a collection of categories.

The EF Core translates our method call to a SQL query, the most efficient way as possible. The query is only executed when you call a method that will transform your data into a collection, or when you use a method to take specific data.

We now have a clean implementation of the categories controller, the service and repository.

We have separated concerns, creating classes that only do what they are supposed to do.

The last step before testing the application is to bind our interfaces to the respective classes using the ASP.NET Core dependency injection mechanism.

Step 6 — Configuring Dependency Injection

It’s time for you to finally understand how this concept works.

In the root folder of the application, open the Startup class. This class is responsible for configuring all kinds of configurations when the application starts.

The ConfigureServices and Configure methods are called at runtime by the framework pipeline to configure how the application should work and which components it must use.

Have a look at the ConfigureServices method. Here we only have one line, that configures the application to use the MVC pipeline, which basically means the application is going to handle requests and responses using controller classes (there are more things happening here behind the scenes, but that’s what you need to know for now).

We can use the ConfigureServices method, accessing the services parameter, to configure our dependency bindings. Clean up the class code removing all comments and change the code as follows:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Domain.Services;
using Supermarket.API.Persistence.Contexts;
using Supermarket.API.Persistence.Repositories;
using Supermarket.API.Services;

namespace Supermarket.API
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services.AddDbContext<AppDbContext>(options => {
                options.UseInMemoryDatabase("supermarket-api-in-memory");
            });

            services.AddScoped<ICategoryRepository, CategoryRepository>();
            services.AddScoped<ICategoryService, CategoryService>();
        }

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

Look at this piece of code:

services.AddDbContext<AppDbContext>(options => {

  options.UseInMemoryDatabase(“supermarket-api-in-memory”);

});

Here we configure the database context. We tell ASP.NET Core to use our AppDbContext with an in-memory database implementation, that is identified by the string passed as an argument to our method. Usually, the in-memory provider is used when we write integration tests, but I’m using it here for simplicity. This way we don’t need to connect to a real database to test the application.

The configuration of these lines internally configures our database context for dependency injection using a scoped lifetime.

The scoped lifetime tells the ASP.NET Core pipeline that every time it needs to resolve a class that receives an instance of AppDbContext as a constructor argument, it should use the same instance of the class. If there is no instance in memory, the pipeline will create a new instance, and reuse it throughout all classes that need it, during a given request. This way, you don’t need to manually create the class instance when you need to use it.

There are other lifetime scopes that you can check reading the official documentation.

The dependency injection technique gives us many advantages, such as:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

After configuring the database context, we also bind our service and repository to the respective classes.

services.AddScoped<ICategoryRepository, CategoryRepository>();

services.AddScoped<ICategoryService, CategoryService>();

Here we also use a scoped lifetime because these classes internally have to use the database context class. It makes sense to specify the same scope in this case.

Now that we configure our dependency bindings, we have to make a small change at the Program class, in order for the database to correctly seed our initial data. This step is only needed when using the in-memory database provider (see this Github issue to understand why).

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Supermarket.API.Persistence.Contexts;

namespace Supermarket.API
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = BuildWebHost(args);

            using(var scope = host.Services.CreateScope())
            using(var context = scope.ServiceProvider.GetService<AppDbContext>())
            {
                context.Database.EnsureCreated();
            }

            host.Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
    }
}

It was necessary to change the Main method to guarantee that our database is going to be “created” when the application starts since we’re using an in-memory provider. Without this change, the categories that we want to seed won’t be created.

With all the basic features implemented, it’s time to test our API endpoint.

Step 7 — Testing the Categories API

Open the terminal or command prompt in the API root folder, and type the following command:

dotnet run

The command above starts the application. The console is going to show an output similar to this:

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]

Entity Framework Core 2.2.0-rtm-35687 initialized ‘AppDbContext’ using provider ‘Microsoft.EntityFrameworkCore.InMemory’ with options: StoreName=supermarket-api-in-memory

info: Microsoft.EntityFrameworkCore.Update[30100]

Saved 2 entities to in-memory store.

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]

User profile is available. Using ‘C:\Users\evgomes\AppData\Local\ASP.NET\DataProtection-Keys’ as key repository and Windows DPAPI to encrypt keys at rest.

Hosting environment: Development

Content root path: C:\Users\evgomes\Desktop\Tutorials\src\Supermarket.API

Now listening on: https://localhost:5001

Now listening on: http://localhost:5000

Application started. Press Ctrl+C to shut down.

You can see that EF Core was called to initialize the database. The last lines show in which ports the application is running.

Open a browser and navigate to http://localhost:5000/api/categories (or to the URL displayed on the console output). If you see a security error because of HTTPS, just add an exception for the application.

The browser is going to show the following JSON data as output:

[
  {
     “id”: 100,
     “name”: “Fruits and Vegetables”,
     “products”: []
  },
  {
     “id”: 101,
     “name”: “Dairy”,
     “products”: []
  }
]

Here we see the data we added to the database when we configured the database context. This output confirms that our code is working.

You created a GET API endpoint with really few lines of code, and you have a code structure that is really easy to change due to the architecture of the API.

Now, it’s time to show you how easy is to change this code when you have to adjust it due to business needs.

Step 8 — Creating a Category Resource

If you remember the specification of the API endpoint, you have noticed that our actual JSON response has one extra property: an array of products. Take a look at the example of the desired response:

{

  [

    { "id": 1, "name": "Fruits and Vegetables" },

    { "id": 2, "name": "Breads" },

    … // Other categories

  ]

}

The products array is present at our current JSON response because our Category model has a Products property, needed by EF Core to correct map the products of a given category.

We don’t want this property in our response, but we can’t change our model class to exclude this property. It would cause EF Core to throw errors when we try to manage categories data, and it would also break our domain model design because it does not make sense to have a product category that doesn’t have products.

To return JSON data containing only the identifiers and names of the supermarket categories, we have to create a resource class.

A resource class is a class that contains only basic information that will be exchanged between client applications and API endpoints, generally in form of JSON data, to represent some particular information.

All responses from API endpoints must return a resource.

It is a bad practice to return the real model representation as the response since it can contain information that the client application does not need or that it doesn’t have permission to have (for example, a user model could return information of the user password, which would be a big security issue).

We need a resource to represent only our categories, without the products.

Now that you know what a resource is, let’s implement it. First of all, stop the running application pressing Ctrl + C at the command line. In the root folder of the application, create a new folder called Resources. There, add a new class called CategoryResource.

namespace Supermarket.API.Resources
{
    public class CategoryResource
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

We have to map our collection of category models, that is provided by our category service, to a collection of category resources.

We’ll use a library called AutoMapper to handle mapping between objects. AutoMapper is a very popular library in the .NET world, and it is used in many commercial and open source projects.

Type the following lines into the command line to add AutoMapper to our application:

dotnet add package AutoMapper

dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

To use AutoMapper, we have to do two things:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

First of all, open the Startup class. In the ConfigureServices method, after the last line, add the following code:

services.AddAutoMapper();

This line handles all necessary configurations of AutoMapper, such as registering it for dependency injection and scanning the application during startup to configure mapping profiles.

Now, in the root directory, add a new folder called Mapping, then add a class called ModelToResourceProfile. Change the code this way:

using AutoMapper;
using Supermarket.API.Domain.Models;
using Supermarket.API.Resources;

namespace Supermarket.API.Mapping
{
    public class ModelToResourceProfile : Profile
    {
        public ModelToResourceProfile()
        {
            CreateMap<Category, CategoryResource>();
        }
    }
}

The class inherits Profile, a class type that AutoMapper uses to check how our mappings will work. On the constructor, we create a map between the Category model class and the CategoryResource class. Since the classes’ properties have the same names and types, we don’t have to use any special configuration for them.

The final step consists of changing the categories controller to use AutoMapper to handle our objects mapping.

using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Services;
using Supermarket.API.Resources;

namespace Supermarket.API.Controllers
{
    [Route("/api/[controller]")]
    public class CategoriesController : Controller
    {
        private readonly ICategoryService _categoryService;
        private readonly IMapper _mapper;

        public CategoriesController(ICategoryService categoryService, IMapper mapper)
        {
            _categoryService = categoryService;
            _mapper = mapper;
        }

        [HttpGet]
        public async Task<IEnumerable<CategoryResource>> GetAllAsync()
        {
            var categories = await _categoryService.ListAsync();
            var resources = _mapper.Map<IEnumerable<Category>, IEnumerable<CategoryResource>>(categories);
            
            return resources;
        }
    }
}

I changed the constructor to receive an instance of IMapper implementation. You can use these interface methods to use AutoMapper mapping methods.

I also changed the GetAllAsync method to map our enumeration of categories to an enumeration of resources using the Map method. This method receives an instance of the class or collection we want to map and, through generic type definitions, it defines to what type of class or collection must be mapped.

Notice that we easily changed the implementation without having to adapt the service class or repository, simply by injecting a new dependency (IMapper) to the constructor.

Dependency injection makes your application maintainable and easy to change since you don’t have to break all your code implementation to add or remove features.

You probably realized that not only the controller class but all classes that receive dependencies (including the dependencies themselves) were automatically resolved to receive the correct classes according to the binding configurations.

Dependency injection is amazing, isn’t it?

Now, start the API again using dotnet run command and head over to http://localhost:5000/api/categories to see the new JSON response.

We already have our GET endpoint. Now, let’s create a new endpoint to POST (create) categories.

Step 9 — Creating new Categories

When dealing with resources creation, we have to care about many things, such as:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

I won’t show how to deal with authorization in this tutorial, but you can see how to implement this feature reading my tutorial on JSON web token authentication.

Let’s write an HTTP POST endpoint that’s going to cover the other scenarios (except for logging, that can change according to different scopes and tools).

Before creating the new endpoint, we need a new resource. This resource will map data that client applications send to this endpoint (in this case, the category name) to a class of our application.

Since we’re creating a new category, we don’t have an ID yet, and it means we need a resource that represents a category containing only its name.

In the Resources folder, add a new class called SaveCategoryResource:

using System.ComponentModel.DataAnnotations;

namespace Supermarket.API.Resources
{
    public class SaveCategoryResource
    {
        [Required]
        [MaxLength(30)]
        public string Name { get; set; }
    }
}

Notice the Required and MaxLength attributes applied over the Name property. These attributes are called data annotations. The ASP.NET Core pipeline uses this metadata to validate requests and responses. As the names suggest, the category name is required and has a max length of 30 characters.

Now let’s define the shape of the new API endpoint. Add the following code to the categories controller:

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] SaveCategoryResource resource)
{
}

We tell the framework that this is an HTTP POST endpoint using the HttpPost attribute.

Notice the response type of this method, Task<IActionResult>. Methods present in controller classes are called actions, and they have this signature because we can return more than one possible result after the application executes the action.

In this case, if the category name is invalid, or if something goes wrong, we have to return a 400 code (bad request) response, containing generally an error message that client apps can use to treat the problem, or we can have a 200 response (success) with data if everything goes ok.

There are many types of action types you can use as response, but generally, we can use this interface, and ASP.NET Core will use a default class for that.

The FromBody attribute tells ASP.NET Core to parse the request body data into our new resource class. It means that when a JSON containing the category name is sent to our application, the framework will automatically parse it to our new class.

Now, let’s implement our route logic. We have to follow some steps to successfully create a new category:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

It seems to be complicated, but it is really easy to implement this logic using the service architecture we structured for our API.

Let’s get started by validating the incoming request.

Step 10 — Validating the Request Body Using the Model State

ASP.NET Core controllers have a property called ModelState. This property is filled during request execution before reaching our action execution. It’s an instance of ModelStateDictionary, a class that contains information such as whether the request is valid and potential validation error messages.

Change the endpoint code as follows:

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] SaveCategoryResource resource)
{
	if (!ModelState.IsValid)
		return BadRequest(ModelState.GetErrorMessages());
}

The code checks if the model state (in this case, the data sent in the request body) is invalid, checking our data annotations. If it isn’t, the API returns a bad request (with 400 status code) and the default error messages our annotations metadata provided.

The ModelState.GetErrorMessages()method isn’t implemented yet. It’s an extension method (a method that extends the functionality of an already existing class or interface) that I’m going to implement to convert the validation errors into simple strings to return to the client.

Add a new folder Extensions in the root of our API and then add a new class ModelStateExtensions.

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Supermarket.API.Extensions
{
    public static class ModelStateExtensions
    {
        public static List<string> GetErrorMessages(this ModelStateDictionary dictionary)
        {
            return dictionary.SelectMany(m => m.Value.Errors)
                             .Select(m => m.ErrorMessage)
                             .ToList();
        }
    }
}

All extension methods should be static, as well as the classes where they are declared.It means they don’t handle specific instance data and that they’re loaded only once when the application starts.

The this keyword in front of the parameter declaration tells the C# compiler to treat it as an extension method. The result is that we can call it like a normal method of this class since we include the respective usingdirective where we want to use the extension.

The extension uses LINQ queries, a very useful feature of .NET that allows us to query and transform data using chainable expressions. The expressions here transform the validation error methods into a list of strings containing the error messages.

Import the namespace Supermarket.API.Extensions into the categories controller before going to the next step.

using Supermarket.API.Extensions;

Let’s continue implementing our endpoint logic by mapping our new resource to a category model class.

Step 11 — Mapping the new Resource

We have already defined a mapping profile to transform models into resources. Now we need a new profile that does the inverse.

Add a new class ResourceToModelProfile into the Mapping folder:

using AutoMapper;
using Supermarket.API.Domain.Models;
using Supermarket.API.Resources;

namespace Supermarket.API.Mapping
{
    public class ResourceToModelProfile : Profile
    {
        public ResourceToModelProfile()
        {
            CreateMap<SaveCategoryResource, Category>();
        }
    }
}

Nothing new here. Thanks to the magic of dependency injection, AutoMapper will automatically register this profile when the application starts, and we don’t have to change any other place to use it.

Now we can map our new resource to the respective model class:

HttpPost]
public async Task<IActionResult> PostAsync([FromBody] SaveCategoryResource resource)
{
	if (!ModelState.IsValid)
		return BadRequest(ModelState.GetErrorMessages());

	var category = _mapper.Map<SaveCategoryResource, Category>(resource);
}

Step 12 — Applying the Request-Response Pattern to Handle the Saving Logic

Now we have to implement the most interesting logic: to save a new category. We expect our service to do it.

The saving logic may fail due to problems when connecting to the database, or maybe because any internal business rule invalidates our data.

If something goes wrong, we can’t simply throw an error, because it could stop the API, and the client application wouldn’t know how to handle the problem. Also, we potentially would have some logging mechanism that would log the error.

The contract of the saving method, it means, the signature of the method and response type, needs to indicate us if the process was executed correctly. If the process goes ok, we’ll receive the category data. If not, we have to receive, at least, an error message telling why the process failed.

We can implement this feature by applying the request-response pattern. This enterprise design pattern encapsulates our request and response parameters into classes as a way to encapsulate information that our services will use to process some task and to return information to the class that is using the service.

This pattern gives us some advantages, such as:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

Let’s create a standard response type for our services methods that handle data changes. For every request of this type, we want to know if the request is executed with no problems. If it fails, we want to return an error message to the client.

In the Domain folder, inside Services, add a new directory called Communication. Add a new class there called BaseResponse.

namespace Supermarket.API.Domain.Services.Communication
{
    public abstract class BaseResponse
    {
        public bool Success { get; protected set; }
        public string Message { get; protected set; }

        public BaseResponse(bool success, string message)
        {
            Success = success;
            Message = message;
        }
    }
}

That’s an abstract class that our response types will inherit.

The abstraction defines a Success property, which will tell whether requests were completed successfully, and a Message property, that will have the error message if something fails.

Notice that these properties are required and only inherited classes can set this data because children classes have to pass this information through the constructor function.

Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.> Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.
Now, in the same folder, add a new class called SaveCategoryResponse.

using Supermarket.API.Domain.Models;

namespace Supermarket.API.Domain.Services.Communication
{
    public class SaveCategoryResponse : BaseResponse
    {
        public Category Category { get; private set; }

        private SaveCategoryResponse(bool success, string message, Category category) : base(success, message)
        {
            Category = category;
        }

        /// <summary>
        /// Creates a success response.
        /// </summary>
        /// <param name="category">Saved category.</param>
        /// <returns>Response.</returns>
        public SaveCategoryResponse(Category category) : this(true, string.Empty, category)
        { }

        /// <summary>
        /// Creates am error response.
        /// </summary>
        /// <param name="message">Error message.</param>
        /// <returns>Response.</returns>
        public SaveCategoryResponse(string message) : this(false, message, null)
        { }
    }
}

The response type also sets a Category property, which is going to contain our category data if the request successfully finishes.

Notice that I’ve defined three different constructors for this class:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

Because C# supports multiple constructors, we simplified the response creation without defining different method to handle this, just by using different constructors.

Now we can change our service interface to add the new save method contract.

Change the ICategoryService interface as follows:

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Services.Communication;

namespace Supermarket.API.Domain.Services
{
    public interface ICategoryService
    {
         Task<IEnumerable<Category>> ListAsync();
         Task<SaveCategoryResponse> SaveAsync(Category category);
    }
}

We’ll simply pass a category to this method and it will handle all logic necessary to save the model data, orchestrating repositories and other necessary services to do that.

Notice I’m not creating a specific request class here since we don’t need any other parameters to perform this task. There is a concept in computer programming called KISS — short for Keep it Simple, Stupid. Basically, it says that you should keep your application as simple as possible.

Remember this when designing your applications: apply only what you need to solve a problem. Don’t over-engineer your application.

Now we can finish our endpoint logic:

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] SaveCategoryResource resource)
{
	if (!ModelState.IsValid)
		return BadRequest(ModelState.GetErrorMessages());

	var category = _mapper.Map<SaveCategoryResource, Category>(resource);
	var result = await _categoryService.SaveAsync(category);

	if (!result.Success)
		return BadRequest(result.Message);

	var categoryResource = _mapper.Map<Category, CategoryResource>(result.Category);
	return Ok(categoryResource);
}

After validating the request data and mapping the resource to our model, we pass it to our service to persist the data.

If something fails, the API returns a bad request. If not, the API maps the new category (now including data such as the new Id) to our previously created CategoryResource and sends it to the client.

Now let’s implement the real logic for the service.

Step 13 — The Database Login and the Unit of Work Pattern

Since we’re going to persist data into the database, we need a new method in our repository.

Add a new AddAsync method to the ICategoryRepository interface:

public interface ICategoryRepository
{
	 Task<IEnumerable<Category>> ListAsync();
	 Task AddAsync(Category category);
}

Now, let’s implement this method at our real repository class:

public class CategoryRepository : BaseRepository, ICategoryRepository
{
	public CategoryRepository(AppDbContext context) : base(context)
	{ }

	public async Task<IEnumerable<Category>> ListAsync()
	{
		return await _context.Categories.ToListAsync();
	}

	public async Task AddAsync(Category category)
	{
		await _context.Categories.AddAsync(category);
	}
}

Here we’re simply adding a new category to our set.

When we add a class to a DBSet<>, EF Core starts tracking all changes that happen to our model and uses this data at the current state to generate queries that will insert, update or delete models.

The current implementation simply adds the model to our set, but our data still won’t be saved.

There is a method called SaveChanges present at the context class that we have to call to really execute the queries into the database. I didn’t call it here because a repository shouldn’t persist data, it’s just an in-memory collection of objects.

This subject is very controversial even between experienced .NET developers, but let me explain to you why you shouldn’t call SaveChanges in repository classes.

We can think of a repository conceptually as any other collection present at the .NET framework. When dealing with a collection in .NET (and many other programming languages, such as Javascript and Java), you generally can:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

Think of a list from the real world. Imagine you’re writing a shopping list to buy things at a supermarket (what a coincidence, no?).

In the list, you write all the fruits you need to buy. You can add fruits to this list, remove a fruit if you give up buying it, or you can replace a fruit’s name. But you can’t save fruits into the list. It doesn’t make sense to say such a thing in plain English.

Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.> Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.
If you want to “save” the fruits lists (in this case, to buy all fruits), you pay it and the supermarket processes the stock data to check if they have to buy more fruits from a provider or not.

The same logic can be applied when programming. Repositories shouldn’t save, update or delete data. Instead, they should delegate it to a different class to handle this logic.

There is another problem when saving data directly into a repository: you can’t use transactions.

Imagine that our application has a logging mechanism that stores some username and the action performed every time a change is made to the API data.

Now imagine that, for some reason, you have a call to a service that updates the username (it’s not a common scenario, but let’s consider it).

You agree that to change the username in a fictional users table, you first have to update all logs to correctly tell who performed that operation, right?

Now imagine we have implemented the update method for users and logs in different repositories, and them both call SaveChanges. What happens if one of these methods fails in the middle of the updating process? You’ll end up with data inconsistency.

We should save our changes into the database only after everything finishes. To do this, we have to use a transaction, that is basically a feature most databases implement to save data only after a complex operation finishes.

“ - Ok, so if we can’t save things here, where should we do it?”

A common pattern to handle this issue is the Unit of Work Pattern. This pattern consists of a class that receives our AppDbContext instance as a dependency and exposes methods to start, complete or abort transactions.

We’ll use a simple implementation of a unit of work to approach our problem here.

Add a new interface inside the Repositories folder of the Domain layer called IUnitOfWork:

using System.Threading.Tasks;

namespace Supermarket.API.Domain.Repositories
{
    public interface IUnitOfWork
    {
         Task CompleteAsync();
    }
}

As you can see, it only exposes a method that will asynchronously complete data management operations.

Let’s add the real implementation now.

Add a new class called UnitOfWork at the RepositoriesRepositories folder of the Persistence layer:

using System.Threading.Tasks;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Persistence.Contexts;

namespace Supermarket.API.Persistence.Repositories
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly AppDbContext _context;

        public UnitOfWork(AppDbContext context)
        {
            _context = context;     
        }

        public async Task CompleteAsync()
        {
            await _context.SaveChangesAsync();
        }
    }
}

That’s a simple, clean implementation that will only save all changes into the database after you finish modifying it using your repositories.

If you research implementations of the Unit of Work pattern, you’ll find more complex ones implementing rollback operations.

Since EF Core already implement the repository pattern and unit of work behind the scenes, we don’t have to care about a rollback method.

“ - What? So why do we have to create all these interfaces and classes?”

Separating the persistence logic from business rules gives many advantages in terms of code reusability and maintenance. If we use EF Core directly, we’ll end up having more complex classes that won’t be so easy to change.

Imagine that in the future you decide to change the ORM framework to a different one, such as Dapper, for example, or if you have to implement plain SQL queries because of performance. If you couple your queries logic to your services, it will be difficult to change the logic, because you’ll have to do it in many classes.

Using the repository pattern, you can simply implement a new repository class and bind it using dependency injection.

So, basically, if you use EF Core directly into your services and you have to change something, that’s what you’ll get:

As I said, EF Core implements the Unit of Work and Repository patterns behind the scenes. We can consider our DbSet<>properties as repositories. Also, SaveChanges only persists data in case of success for all database operations.

Now that you know what is a unit of work and why to use it with repositories, let’s implement the real service’s logic.

public class CategoryService : ICategoryService
{
	private readonly ICategoryRepository _categoryRepository;
	private readonly IUnitOfWork _unitOfWork;

	public CategoryService(ICategoryRepository categoryRepository, IUnitOfWork unitOfWork)
	{
		_categoryRepository = categoryRepository;
		_unitOfWork = unitOfWork;
	}

	public async Task<IEnumerable<Category>> ListAsync()
	{
		return await _categoryRepository.ListAsync();
	}

	public async Task<SaveCategoryResponse> SaveAsync(Category category)
	{
		try
		{
			await _categoryRepository.AddAsync(category);
			await _unitOfWork.CompleteAsync();
			
			return new SaveCategoryResponse(category);
		}
		catch (Exception ex)
		{
			// Do some logging stuff
			return new SaveCategoryResponse($"An error occurred when saving the category: {ex.Message}");
		}
	}
}

Thanks to our decoupled architecture, we can simply pass an instance of UnitOfWork as a dependency for this class.

Our business logic is pretty simple.

First, we try to add the new category to the database and then the API try to save it, wrapping everything inside a try-catch block.

If something fails, the API calls some fictional logging service and return a response indicating failure.

If the process finishes with no problems, the application returns a success response, sending our category data. Simple, right?

Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.> Notice: we’re manually setting the Id properties here because the in-memory provider requires it to work. I’m setting the identifiers to big numbers to avoid collision between auto-generated identifiers and seed data.
The last step before testing our API is to bind the unit of work interface to its respective class.

Add this new line to the ConfigureServices method of the Startup class:

services.AddScoped<IUnitOfWork, UnitOfWork>();

Now let’s test it!

Step 14 — Testing our POST Endpoint using Postman

Start our application again using dotnet run.

We can’t use the browser to test a POST endpoint. Let’s use Postman to test our endpoints. It’s a very useful tool to test RESTful APIs.

Open Postman and close the intro messages. You’ll see a screen like this one:

Change the GET selected by default into the select box to POST.

Type the API address into the Enter request URL field.

We have to provide the request body data to send to our API. Click on the Body menu item, then change the option displayed below it to raw.

Postman will show a Text option in the right. Change it to JSON (application/json) and paste the following JSON data below:

{

  "name": ""

}

As you see, we’re going to send an empty name string to our new endpoint.

Click the Send button. You’ll receive an output like this:

Do you remember the validation logic we created for the endpoint? This output is the proof it works!

Notice also the 400 status code displayed at the right. The BadRequest result automatically adds this status code to the response.

Now let’s change the JSON data to a valid one to see the new response:

The API correctly created our new resource.

Until now, our API can list and create categories. You learned a lot of things about the C# language, the ASP.NET Core framework and also common design approaches to structure your APIs.

Let’s continue our categories API creating the endpoint to update categories.

From now on, since I explained you most concepts, I’ll speed up the explanations and focus on new subjects to not waste your time. Let’s go!

Step 15 — Updating Categories

To update categories, we need an HTTP PUT endpoint.

The logic we have to code is very similar to the POST one:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

Let’s add the new PutAsync method into the controller class:

[HttpPut("{id}")]
public async Task<IActionResult> PutAsync(int id, [FromBody] SaveCategoryResource resource)
{
	if (!ModelState.IsValid)
		return BadRequest(ModelState.GetErrorMessages());

	var category = _mapper.Map<SaveCategoryResource, Category>(resource);
	var result = await _categoryService.UpdateAsync(id, category);

	if (!result.Success)
		return BadRequest(result.Message);

	var categoryResource = _mapper.Map<Category, CategoryResource>(result.Category);
	return Ok(categoryResource);
}

If you compare it with the POST logic, you’ll notice we have only one difference here: the HttPut attribute specifies a parameter that the given route should receive.

We’ll call this endpoint specifying the category Id as the last URL fragment, like /api/categories/1. The ASP.NET Core pipeline parse this fragment to the parameter of the same name.

Now we have to define the UpdateAsync method signature into the ICategoryService interface:

public interface ICategoryService
{
	Task<IEnumerable<Category>> ListAsync();
	Task<SaveCategoryResponse> SaveAsync(Category category);
	Task<SaveCategoryResponse> UpdateAsync(int id, Category category);
}

Now let’s move to the real logic.

Step 16 — The Update Logic

To update our category, first, we need to return the current data from the database, if it exists. We also need to update it into our DBSet<>.

Let’s add two new method contracts to our ICategoryService interface:

public interface ICategoryRepository
{
	Task<IEnumerable<Category>> ListAsync();
	Task AddAsync(Category category);
	Task<Category> FindByIdAsync(int id);
	void Update(Category category);
}

We’ve defined the FindByIdAsync method, that will asynchronously return a category from the database, and the Update method. Pay attention that the Update method isn’t asynchronous since the EF Core API does not require an asynchronous method to update models.

Now let’s implement the real logic into the CategoryRepository class:

public async Task<Category> FindByIdAsync(int id)
{
	return await _context.Categories.FindAsync(id);
}

public void Update(Category category)
{
	_context.Categories.Update(category);
}

Finally we can code the service logic:

public async Task<SaveCategoryResponse> UpdateAsync(int id, Category category)
{
	var existingCategory = await _categoryRepository.FindByIdAsync(id);

	if (existingCategory == null)
		return new SaveCategoryResponse("Category not found.");

	existingCategory.Name = category.Name;

	try
	{
		_categoryRepository.Update(existingCategory);
		await _unitOfWork.CompleteAsync();

		return new SaveCategoryResponse(existingCategory);
	}
	catch (Exception ex)
	{
		// Do some logging stuff
		return new SaveCategoryResponse($"An error occurred when updating the category: {ex.Message}");
	}
}

The API tries to get the category from the database. If the result is null, we return a response telling that the category does not exist. If the category exists, we need to set its new name.

The API, then, tries to save changes, like when we create a new category. If the process completes, the service returns a success response. If not, the logging logic executes, and the endpoint receives a response containing an error message.

Now let’s test it. First, let’s add a new category to have a valid Id to use. We could use the identifiers of the categories we seed to our database, but I want to do it this way to show you that our API is going to update the correct resource.

Run the application again and, using Postman, POST a new category to the database:

Having a valid Id in hands, change the POST option to PUT into the select box and add the ID value at the end of the URL. Change the name property to a different name and send the request to check the result:

You can send a GET request to the API endpoint to assure you correctly edited the category name:

The last operation we have to implement for categories is the exclusion of categories. Let’s do it creating an HTTP Delete endpoint.

Step 17 — Deleting Categories

The logic for deleting categories is really easy to implement since most methods we need were built previously.

These are the necessary steps for our route to work:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

Let’s get started by adding the new endpoint logic:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteAsync(int id)
{
	var result = await _categoryService.DeleteAsync(id);

	if (!result.Success)
		return BadRequest(result.Message);

	var categoryResource = _mapper.Map<Category, CategoryResource>(result.Category);
	return Ok(categoryResource);
}

The HttpDelete attribute also defines an id template.

Before adding the DeleteAsync signature to our ICategoryService interface, we need to do a small refactoring.

The new service method must return a response containing the category data, the same way we did for the PostAsync and UpdateAsync methods. We could reuse the SaveCategoryResponse for this purpose, but we’re not saving data in this case.

To avoid creating a new class with the same shape to deliver this requirement, we can simply rename our SaveCategoryResponse to CategoryResponse.

If you’re using Visual Studio Code, you can open the SaveCategoryResponse class, put the mouse cursor above the class name and use the option Change All Occurrencesto rename the class:

Be sure to rename the filename too.

Let’s add the DeleteAsync method signature to the ICategoryService interface:

public interface ICategoryService
{
	Task<IEnumerable<Category>> ListAsync();
	Task<CategoryResponse> SaveAsync(Category category);
	Task<CategoryResponse> UpdateAsync(int id, Category category);
	Task<CategoryResponse> DeleteAsync(int id);
}

Before implementing the deletion logic, we need a new method in our repository.

Add the Remove method signature to the ICategoryRepository interface:

void Remove(Category category);

And now add the real implementation on the repository class:

public void Remove(Category category)
{
	_context.Categories.Remove(category);
}

EF Core requires the instance of our model to be passed to the Remove method to correctly understand which model we’re deleting, instead of simply passing an Id.

Finally, let’s implement the logic on CategoryService class:

public async Task<CategoryResponse> DeleteAsync(int id)
{
	var existingCategory = await _categoryRepository.FindByIdAsync(id);

	if (existingCategory == null)
		return new CategoryResponse("Category not found.");

	try
	{
		_categoryRepository.Remove(existingCategory);
		await _unitOfWork.CompleteAsync();

		return new CategoryResponse(existingCategory);
	}
	catch (Exception ex)
	{
		// Do some logging stuff
		return new CategoryResponse($"An error occurred when deleting the category: {ex.Message}");
	}
}

There’s nothing new here. The service tries to find the category by ID and then it calls our repository to delete the category. Finally, the unit of work completes the transaction executing the real operation into the database.

“ - Hey, but how about the products of each category? Don’t you need to create a repository and delete the products first, to avoid errors?”

The answer is no. Thanks to EF Core tracking mechanism, when we load a model from the database, the framework knows which relationships the model has. If we delete it, EF Core knows it should delete all related models first, recursively.

We can disable this feature when mapping our classes to database tables, but it’s out of scope for this tutorial. Take a look here if you want to learn about this feature.

Now it’s time to test our new endpoint. Run the application again and send a DELETE request using Postman as follows:

We can check that our API is correctly working by sending a GET request:

We’ve finished the categories API. Now it’s time to move to the products API.

Step 18 — The Products API

So far you have learned how to implement all basic HTTP verbs to handle CRUD operations with ASP.NET Core. Let’s go to the next level implementing our products API.

I won’t detail all HTTP verbs again because it would be exhaustive. For the final part of this tutorial, I’ll cover only the GET request, to show you how to include related entities when querying data from the database and how to use the Description attributes we defined for the EUnitOfMeasurement enumeration values.

Add a new controller into the Controllers folder called ProductsController.

Before coding anything here, we have to create the product resource.

Let me refresh your memory showing again how our resource should look like:

{
 [
  {
   "id": 1,
   "name": "Sugar",
   "quantityInPackage": 1,
   "unitOfMeasurement": "KG"
   "category": {
   "id": 3,
   "name": "Sugar"
   }
  },
  … // Other products
 ]
}

We want a JSON array containing all products from the database.

The JSON data differs from the product model by two things:

  • Create a RESTful service that allows client applications to manage the supermarket’s product catalog. It needs to expose endpoints to create, read, edit and delete products categories, such as dairy products and cosmetics, and also to manage products of these categories.
  • For categories, we need to store their names. For products, we need to store their names, unit of measurement (for example, KG for products measured by weight), quantity in the package (for example, 10 if the product is a pack of biscuits) and their respective categories.

To represent the unit of measurement, we can use a simple string property instead of an enum type (by the way, we don’t have a default enum type for JSON data, so we have to transform it into a different type).

Now that we now how to shape the new resource, let’s create it. Add a new class ProductResource into the Resources folder:

namespace Supermarket.API.Resources
{
    public class ProductResource
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int QuantityInPackage { get; set; }
        public string UnitOfMeasurement { get; set; }
        public CategoryResource Category {get;set;}
    }
}

Now we have to configure the mapping between the model class and our new resource class.

The mapping configuration will be almost the same as the ones used for other mappings, but here we have to handle the transformation of our EUnitOfMeasurement enum to a string.

Do you remember the StringValue attribute applied over the enumeration types? Now I’ll show you how to extract this information using a powerful feature of the .NET framework: the Reflection API.

The Reflection API is a powerful set of resources that allows us to extract and manipulate metadata. A lot of frameworks and libraries (including ASP.NET Core itself) make use of these resources to handle many things behind the scenes.

Now let’s see how it works in practice. Add a new class into the Extensions folder called EnumExtensions.

using System.ComponentModel;
using System.Reflection;

namespace Supermarket.API.Extensions
{
    public static class EnumExtensions
    {
        public static string ToDescriptionString<TEnum>(this TEnum @enum)
        {
            FieldInfo info = @enum.GetType().GetField(@enum.ToString());
            var attributes = (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute), false);

            return attributes?[0].Description ?? @enum.ToString();
        }
    }
}

It may seem scaring the first time you look at the code, but it’s not so complex. Let’s break down the code definition to understand how it works.

First, we defined a generic method (a method that can receive more than one type of argument, in this case, represented by the TEnum declaration) that receives a given enum as an argument.

Since enum is a reserved keyword in C#, we added an @ in front of the parameter’s name to make it a valid name.

The first execution step of this method is to get the type information (the class, interface, enum or struct definition) of the parameter using the GetType method.

Then, the method gets the specific enumeration value (for instance, Kilogram) using GetField(@enum.ToString()).

The next line finds all Description attributes applied over the enumeration value and stores their data into an array (we can specify multiple attributes for a same property in some cases).

The last line uses a shorter syntax to check if we have at least one description attribute for the enumeration type. If we have, we return the Description value provided by this attribute. If not, we return the enumeration as a string, using the default casting.

The ?. operator (a null-conditional operator) checks if the value is null before accessing its property.

The ?? operator (a null-coalescing operator) tells the application to return the value at the left if it’s not empty, or the value at right otherwise.

Now that we have an extension method to extract descriptions, let’s configure our mapping between model and resource. Thanks to AutoMapper, we can do it with only one extra line.

Open the ModelToResourceProfile class and change the code this way:

using AutoMapper;
using Supermarket.API.Domain.Models;
using Supermarket.API.Extensions;
using Supermarket.API.Resources;

namespace Supermarket.API.Mapping
{
    public class ModelToResourceProfile : Profile
    {
        public ModelToResourceProfile()
        {
            CreateMap<Category, CategoryResource>();

            CreateMap<Product, ProductResource>()
                .ForMember(src => src.UnitOfMeasurement,
                           opt => opt.MapFrom(src => src.UnitOfMeasurement.ToDescriptionString()));
        }
    }
}

This syntax tells AutoMapper to use the new extension method to convert our EUnitOfMeasurement value into a string containing its description. Simple, right? You can read the official documentation to understand the full syntax.

Notice we haven’t defined any mapping configuration for the category property. Because we previously configured the mapping for categories and because the product model has a category property of the same type and name, AutoMapper implicitly knows that it should map it using the respective configuration.

Now let’s add the endpoint code. Change the ProductsController code:

using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Services;
using Supermarket.API.Resources;

namespace Supermarket.API.Controllers
{
    [Route("/api/[controller]")]
    public class ProductsController : Controller
    {
        private readonly IProductService _productService;
        private readonly IMapper _mapper;

        public ProductsController(IProductService productService, IMapper mapper)
        {
            _productService = productService;
            _mapper = mapper;
        }

        [HttpGet]
        public async Task<IEnumerable<ProductResource>> ListAsync()
        {
            var products = await _productService.ListAsync();
            var resources = _mapper.Map<IEnumerable<Product>, IEnumerable<ProductResource>>(products);
            return resources;
        }
    }
}

Basically, the same structure defined for the categories controller.

Let’s go to the service part. Add a new IProductService interface into the Services folder present at the Domain layer:

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;

namespace Supermarket.API.Domain.Services
{
    public interface IProductService
    {
         Task<IEnumerable<Product>> ListAsync();
    }
}

You should have realized we need a repository before really implementing the new service.

Add a new interface called IProductRepository into the respective folder:

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;

namespace Supermarket.API.Domain.Repositories
{
    public interface IProductRepository
    {
         Task<IEnumerable<Product>> ListAsync();
    }
}

Now let’s implement the repository. We have to implement it almost the same way we did for the categories repository, except that we need to return the respective category data of each product when querying data.

EF Core, by default, does not include related entities to your models when you querying data because it could be very slow (imagine a model with ten related entities, all all related entities having its own relationships).

To include the categories data, we need only one extra line:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Persistence.Contexts;

namespace Supermarket.API.Persistence.Repositories
{
    public class ProductRepository : BaseRepository, IProductRepository
    {
        public ProductRepository(AppDbContext context) : base(context)
        {
        }

        public async Task<IEnumerable<Product>> ListAsync()
        {
            return await _context.Products.Include(p => p.Category)
                                          .ToListAsync();
        }
    }
}

Notice the call to Include(p => p.Category). We can chain this syntax to include as many entities as necessary when querying data. EF Core is going to translate it to a join when performing the select.

Now we can implement the ProductService class the same way we did for categories:

using System.Collections.Generic;
using System.Threading.Tasks;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Domain.Services;

namespace Supermarket.API.Services
{
    public class ProductService : IProductService
    {
        private readonly IProductRepository _productRepository;
    
        public ProductService(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }

        public async Task<IEnumerable<Product>> ListAsync()
        {
            return await _productRepository.ListAsync();
        }
    }
}

Let’s bind the new dependencies changing the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddDbContext<AppDbContext>(options =>
    {
        options.UseInMemoryDatabase("supermarket-api-in-memory");
    });

    services.AddScoped<ICategoryRepository, CategoryRepository>();
    services.AddScoped<IProductRepository, ProductRepository>();
    services.AddScoped<IUnitOfWork, UnitOfWork>();

    services.AddScoped<ICategoryService, CategoryService>();
    services.AddScoped<IProductService, ProductService>();

    services.AddAutoMapper();
}

Finally, before testing the API, let’s change the AppDbContext class to include some products when initializing the application so we can see the results:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    
    builder.Entity<Category>().ToTable("Categories");
    builder.Entity<Category>().HasKey(p => p.Id);
    builder.Entity<Category>().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd().HasValueGenerator<InMemoryIntegerValueGenerator<int>>();
    builder.Entity<Category>().Property(p => p.Name).IsRequired().HasMaxLength(30);
    builder.Entity<Category>().HasMany(p => p.Products).WithOne(p => p.Category).HasForeignKey(p => p.CategoryId);

    builder.Entity<Category>().HasData
    (
        new Category { Id = 100, Name = "Fruits and Vegetables" }, // Id set manually due to in-memory provider
        new Category { Id = 101, Name = "Dairy" }
    );

    builder.Entity<Product>().ToTable("Products");
    builder.Entity<Product>().HasKey(p => p.Id);
    builder.Entity<Product>().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd();
    builder.Entity<Product>().Property(p => p.Name).IsRequired().HasMaxLength(50);
    builder.Entity<Product>().Property(p => p.QuantityInPackage).IsRequired();
    builder.Entity<Product>().Property(p => p.UnitOfMeasurement).IsRequired();

    builder.Entity<Product>().HasData
    (
        new Product
        {
            Id = 100,
            Name = "Apple",
            QuantityInPackage = 1,
            UnitOfMeasurement = EUnitOfMeasurement.Unity,
            CategoryId = 100
        },
        new Product
        {
            Id = 101,
            Name = "Milk",
            QuantityInPackage = 2,
            UnitOfMeasurement = EUnitOfMeasurement.Liter,
            CategoryId = 101,
        }
    );
}

I added two fictional products associating them to the categories we seed when initializing the application.

Time to test! Run the API again and send a GET request to /api/products using Postman:

And that’s it! Congratulations!

Now you have a base on how to build a RESTful API using ASP.NET Core using a decoupled architecture. You learned many things of the .NET Core framework, how to work with C#, the basics of EF Core and AutoMapper and many useful patterns to use when designing your applications.

You can check the full implementation of the API, containing the others HTTP verbs for products, checking the [Github] (https://github.com/evgomes/supermarket-api “”)repository:

Conclusion

ASP.NET Core is a great framework to use when creating web applications. It comes with many useful APIs you can use to build clean, maintainable applications. Consider it as an option when creating professional applications.

This article hasn’t covered all aspects of a professional API, but you learned all the basics. You also learned many useful patterns to solve patterns we face daily.

I hope you enjoyed this article and I hope it was useful for you. I appreciate your feedback to understand how I can improve this.

References to Keep Learning

.NET Core Tutorials — Microsoft Docs

ASP.NET Core Documentation — Microsoft Docs

Learn More

The Complete ASP.NET MVC 5 Course

Build a Real-world App with ASP.NET Core and Angular 2 (4+)

ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course

Rest Api’s in Asp.Net and C#

Hands on ASP .Net Core 2

Top 5 Common Node.js mistakes in Lambda

Top 5 Common Node.js mistakes in Lambda

Top 5 common mistakes to avoid when working with Node.js in Lambda

I have helped quite a few clients with their Node.js serverless projects. In doing so I have seen some recurring mistakes around async/await.

1. Still using callbacks

Many people are still using the callbacks in their async handler functions:

module.exports.handler = async (event, context, cb) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({ message: 'hello world' })
  }

  cb(null, response)
}

instead of the simpler alternative:

module.exports.handler = async (event, context) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({ message: 'hello world' })
  }

  return response
}

2. Not using promisify

Before Node8, bluebird filled a massive gap. It provided the utility to convert callback-based functions to promise-based. But Node8's built-in util module has filled that gap with the promisify function.

For example, we can now transform the readFile function from the fs module like this:

const fs = require('fs')
const { promisify } = require('util')
const readFile = promisify(fs.readFile)

No need to use bluebird anymore. That's one less dependency, which helps reduce the cold start time for our functions.

3. Too sequential

async/await lets you write asynchronous code as if they're synchronous, which is awesome. No more dealing with callback hell!

On the flip side, we can also miss a trick and not perform tasks concurrently where appropriate.

Take the following code as an example:

async function getFixturesAndTeam(teamId) {
  const fixtures = await fixtureModel.fetchAll()
  const team = await teamModel.fetch(teamId)
  return {
    team,
    fixtures: fixtures.filter(x => x.teamId === teamId)
  }
}

This function is easy to follow, but it's hardly optimal. teamModel.fetch doesn't depend on the result of fixtureModel.fetchAll, so they should run concurrently.

Here is how you can improve it:

async function getFixturesAndTeam(teamId) {
  const fixturesPromise = fixtureModel.fetchAll()
  const teamPromise = teamModel.fetch(teamId)

  const fixtures = await fixturesPromise
  const team = await teamPromise

  return {
    team,
    fixtures: fixtures.filter(x => x.teamId === teamId)
  }
}

In this version, both fixtureModel.fetchAll and teamModel.fetch are started concurrently.

You also need to watch out when using map with async/await. The following will call teamModel.fetch one after another:

async function getTeams(teamIds) {
  const teams = _.map(teamIds, id => await teamModel.fetch(id))
  return teams
}

Instead, you should write it as the following:

async function getTeams(teamIds) {
  const promises = _.map(teamIds, id => teamModel.fetch(id))
  const teams = await Promise.all(promises)
  return teams
}

In this version, we map teamIds to an array of Promise. We can then use Promise.all to turn this array into a single Promise that returns an array of teams.

In this case, teamModel.fetch is called concurrently and can significantly improve execution time.

4. async/await inside forEach()

This is a tricky one, and can sometimes catch out even experienced Node.js developers.

The problem is that code like this doesn't behave the way you'd expect it to:

[ 1, 2, 3 ].forEach(async (x) => {
  await sleep(x)
  console.log(x)
})

console.log('all done.')

When you run this you'll get the following output:

all done.

See this post for a longer explanation about why this doesn't work. For now, just remember to avoid using async/await inside a forEach!

5. Not using AWSSDK’s .promise()

Did you know that the AWS SDK clients support both callbacks and promises? To use async/await with the AWS SDK, add .promise() to client methods like this:

const AWS = require('aws-sdk')
const Lambda = new AWS.Lambda()

async function invokeLambda(functionName) {
  const req = {
    FunctionName: functionName,
    Payload: JSON.stringify({ message: 'hello world' })
  }
  await Lambda.invoke(req).promise()
}

No more callback functions, yay!

Building Web App using ASP.NET Web API Angular 7 and SQL Server

Building Web App using ASP.NET Web API Angular 7 and SQL Server

The comprehensive step by step tutorial on building Web Application using ASP.NET Web API, Angular 7 and Microsoft SQL Server

The comprehensive step by step tutorial on building Web Application using ASP.NET Web API, Angular 7 and Microsoft SQL Server. In this tutorial, we will create a RESTful API web service using ASP.NET Core Web API then create a front-end application with Angular 7. For the backend, we use Microsoft SQL Server 2017 Express Edition with the additional database Northwind. This is our first ASP.NET tutorial, so there will be many shortcomings and a lot of discussions.

Table of Contents:

The following tools, frameworks, package, and modules are required for this tutorial:

We assume that you already install all above required tools. Now, we have to check that DotNet SDK already installed and added to the path. Open command prompt then type this command to make sure DotNet installed and runnable via command prompt.

dotnet --version

You will get this result.

2.1.403

We are using DotNet Framework SDK version 2.1.403. That’s mean you’re ready to move to the main steps.

1. Download and Install Northwind Sample Database

To download Northwind sample database for SQL Server 2017, open your browser then go to this link https://northwinddatabase.codeplex.com/. After download, extract to your root folder (for example C:). Next, open Microsoft SQL Server Management Studio. Login to your local Server then right-click Database in the Object Explorer Window then choose Restore Database.

Choose Device then Click ... button. The select device dialog will open then click Add button, browse to the .bak file that previously extracted then click OK button then click again OK button.

Click OK button to Restore the Northwind Database. Now, you should see the Northwind Database in the Database.

2. Create and Configure a new ASP.NET Web API Application

A slightly different of ASP.NET Core application creation, we will use the command prompt. Of course, you can use the more easiest way when using Microsoft Visual Studio. In the command prompt go to your projects folder then type this command.

dotnet &nbsp;new webapi –o AspNetAngular –n AspNetAngular

You will the output like below in the Command Prompt.

The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on AspNetAngular\AspNetAngular.csproj...
&nbsp; Restoring packages for C:\Users\DIDIN\Projects\AspNetAngular\AspNetAngular.csproj...
&nbsp; Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngular\obj\AspNetAngular.csproj.nuget.g.props.
&nbsp; Generating MSBuild file C:\Users\DIDIN\Projects\AspNetAngular\obj\AspNetAngular.csproj.nuget.g.targets.
&nbsp; Restore completed in 5.82 sec for C:\Users\DIDIN\Projects\AspNetAngular\AspNetAngular.csproj.

Restore succeeded.

That’s mean, you can open directly as a folder from Visual Studio Code as well as CSharp Project/Solution from Microsoft Visual Studio. Or you can call this new ASP.NET Core Web API Project by type this command.

cd AspNetAngular
code .

The project will be opened by Visual Studio Code. If you get a notification like below just click Yes button.

Next, open terminal from the menu or press Ctrl+Shift+`. Run these commands to installs all required packages

dotnet add package Microsoft.EntityFrameworkCore.Tools -v 2.1.2
dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 2.1.2
dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design -v 1.1.6
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design -v 2.1.5
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Tools -v 2.0.4
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection -v 5.0.1

If you see a notification like below, just click Restore button.

Now, you will have this packages with the right version installed shown in AspNetAngular.csproj file.

<ItemGroup>
&nbsp; <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="6.0.0" />
&nbsp; <PackageReference Include="Microsoft.AspNetCore.App" />
&nbsp; <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
&nbsp; <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.2" />
&nbsp; <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.6" />
&nbsp; <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2">
&nbsp; &nbsp; <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
&nbsp; &nbsp; <PrivateAssets>all</PrivateAssets>
&nbsp; </PackageReference>
&nbsp; <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.5" />
&nbsp; <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" />
</ItemGroup>

Next, build the DotNet application to make sure there’s no error in package dependencies.

dotnet build

You must see this output when everything on the right path.

Build succeeded.
&nbsp; &nbsp; 0 Warning(s)
&nbsp; &nbsp; 0 Error(s)

Next, we have to configure connections to Microsoft SQL Server Database. First, open and edit appsettings.json then add this lines before logging JSON object.

"ConnectionStrings": {
&nbsp; "SQLConnection": "Server=.;Database=NORTHWND;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;MultipleActiveResultSets=true"
},

Open and edit Startup.cs then add this line inside ConfigureServices bracket.

services.AddDbContext<NORTHWNDContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SQLConnection")));

Next, add this line to enable CORS after above line.

services.AddCors();

Also, add this line inside Configure bracket.

app.UseCors(x => x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

Don’t forget to import the library or module that added in the Configuration.

3. Generate Models from Microsoft SQL Server Database

We will use the Code Generation Tools package to generate all models that represent all tables in the Northwind Database. Run this command to generate the models and their context.

dotnet ef dbcontext scaffold "Server=.;Database=NORTHWND;Trusted_Connection=True;User Id=sa;Password=q;Integrated Security=false;" Microsoft.EntityFrameworkCore.SqlServer -o Models

You will see the generated Models and Context inside the Models folder.

If there’s a lot of warning or error that comes from IDE linter, just re-open the Visual Studio Code. As you see in the table of contents, we will use only Supplier model for this tutorial. The generated suppliers look like this.

using System;
using System.Collections.Generic;

namespace AspNetAngular.Models
{
&nbsp; &nbsp; public partial class Suppliers
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; public Suppliers()
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Products = new HashSet<Products>();
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public int SupplierId { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Region { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Country { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Phone { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Fax { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string HomePage { get; set; }

&nbsp; &nbsp; &nbsp; &nbsp; public ICollection<Products> Products { get; set; }
&nbsp; &nbsp; }
}

That model declared in the NORTHWNDContext.cs the file inside Models folder.

public virtual DbSet<Suppliers> Suppliers { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
&nbsp; modelBuilder.Entity<Suppliers>(entity =>
&nbsp; {
&nbsp; &nbsp; &nbsp; entity.HasKey(e => e.SupplierId);

&nbsp; &nbsp; &nbsp; entity.HasIndex(e => e.CompanyName)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .HasName("CompanyName");

&nbsp; &nbsp; &nbsp; entity.HasIndex(e => e.PostalCode)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .HasName("PostalCode");

&nbsp; &nbsp; &nbsp; entity.Property(e => e.SupplierId).HasColumnName("SupplierID");

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Address).HasMaxLength(60);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.City).HasMaxLength(15);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.CompanyName)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .IsRequired()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .HasMaxLength(40);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.ContactName).HasMaxLength(30);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.ContactTitle).HasMaxLength(30);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Country).HasMaxLength(15);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Fax).HasMaxLength(24);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.HomePage).HasColumnType("ntext");

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Phone).HasMaxLength(24);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.PostalCode).HasMaxLength(10);

&nbsp; &nbsp; &nbsp; entity.Property(e => e.Region).HasMaxLength(15);
&nbsp; });
}

4. Create DTO (Data Transfer Object) for Request Body and Response

To specify request body and response fields, we will use a Data Transfer Object (DTO). For that, create a new DTOs folder and AddSupplierDto.cs, EditSupplierDto.cs, SupplierResponse.cs files inside that folder. Next, open and edit AddSupplierDto.cs then Replace all codes with this.

using System;
using System.ComponentModel.DataAnnotations;

namespace AspNetAngular.Dtos
{
&nbsp; &nbsp; public class AddSupplierDto
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; [Required]
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Region { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Country { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Phone { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Fax { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string HomePage { get; set; }
&nbsp; &nbsp; }
}

Next, open and edit EditSupplierDto.cs then Replace all codes with this.

using System;
using System.ComponentModel.DataAnnotations;

namespace AspNetAngular.Dtos
{
&nbsp; &nbsp; public class EditSupplierDto
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; [Required]
&nbsp; &nbsp; &nbsp; &nbsp; public int SupplierId { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; [Required]
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Region { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Country { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Phone { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Fax { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string HomePage { get; set; }
&nbsp; &nbsp; }
}

Next, open and edit SupplierResponseDto.cs then Replace all codes with this.

namespace AspNetAngular.Dtos
{
&nbsp; &nbsp; public class SupplierResponseDto
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; public int SupplierId { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string CompanyName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactName { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string ContactTitle { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string Address { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string City { get; set; }
&nbsp; &nbsp; &nbsp; &nbsp; public string PostalCode { get; set; }

&nbsp; &nbsp; }
}

5. Create Helpers Class for Mapping DTO with Model Classes

To create a helper for mapping DTO to Model classes, create the folder Helpers in the root of project folder then create a file AutoMapperProfile.cs inside that new folder. Open and edit this new file then replace all codes with this.

using AspNetAngular.Dtos;
using AspNetAngular.Models;
using AutoMapper;

namespace AspNetAngular.Helpers
{
&nbsp; &nbsp; public class AutoMapperProfiles: Profile
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; public AutoMapperProfiles()
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateMap<AddSupplierDto, Suppliers>();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateMap<EditSupplierDto, Suppliers>();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateMap<Suppliers, SupplierResponseDto>();
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices.

services.AddAutoMapper();

Don’t forget to auto import the library in ConfigureServices.

6. Create Repositories for CRUD (Create, Read, Update Delete) Operations

To create a repository class and interface, we have to create a folder Repositories in the root of the project folder. Next, create an interface file inside that folder with the name IDataRepository.cs then replace all codes with this.

using System.Threading.Tasks;

namespace AspNetAngular.Repositories
{
&nbsp; &nbsp; public interface IDataRepository<T> where T : class
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; void Add(T entity);
&nbsp; &nbsp; &nbsp; &nbsp; void Update(T entity);
&nbsp; &nbsp; &nbsp; &nbsp; void Delete(T entity);
&nbsp; &nbsp; &nbsp; &nbsp; Task<T> SaveAsync(T entity);
&nbsp; &nbsp; }
}

Next, create a file inside that folder with the name DataRepository.cs then replace all codes with this.

using System.Threading.Tasks;
using AspNetAngular.Models;

namespace AspNetAngular.Repositories
{
&nbsp; &nbsp; public class DataRepository<T> : IDataRepository<T> where T : class
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; private readonly NORTHWNDContext _context;

&nbsp; &nbsp; &nbsp; &nbsp; public DataRepository(NORTHWNDContext context)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context = context;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public void Add(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Set<T>().Add(entity);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public void Update(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Set<T>().Update(entity);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public void Delete(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Set<T>().Remove(entity);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; public async Task<T> SaveAsync(T entity)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return entity;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

Next, open and edit again Startup.cs then add this line inside ConfigureServices bracket.

services.AddScoped(typeof(IDataRepository < > ), typeof(DataRepository < > ));

Also, import the required classes and libraries.

7. Create Controller for CRUD Operations

Now, we will show you how the API available via Controller. We will generate a controller for Supplier model. Before generate, we have to install the tools for it. Run this command in the terminal of Visual Studio Code.

dotnet tool install --global dotnet-aspnet-codegenerator --version 2.2.1

Just run this command to generate it.

dotnet aspnet-codegenerator controller -name SupplierController -api -async -m AspNetAngular.Models.Suppliers -dc NORTHWNDContext -namespace AspNetAngular.Controllers -outDir Controllers

Now, we have a Supplier’s controller that looks like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using AspNetAngular.Models;

namespace AspNetAngular.Controllers
{
&nbsp; &nbsp; [Route("api/[controller]")]
&nbsp; &nbsp; [ApiController]
&nbsp; &nbsp; public class SupplierController : ControllerBase
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; private readonly NORTHWNDContext _context;

&nbsp; &nbsp; &nbsp; &nbsp; public SupplierController(NORTHWNDContext context)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context = context;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // GET: api/Supplier
&nbsp; &nbsp; &nbsp; &nbsp; [HttpGet]
&nbsp; &nbsp; &nbsp; &nbsp; public IEnumerable<Suppliers> GetSuppliers()
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return _context.Suppliers;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // GET: api/Supplier/5
&nbsp; &nbsp; &nbsp; &nbsp; [HttpGet("{id}")]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> GetSuppliers([FromRoute] int id)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var suppliers = await _context.Suppliers.FindAsync(id);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (suppliers == null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Ok(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // PUT: api/Supplier/5
&nbsp; &nbsp; &nbsp; &nbsp; [HttpPut("{id}")]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> PutSuppliers([FromRoute] int id, [FromBody] Suppliers suppliers)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (id != suppliers.SupplierId)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Entry(suppliers).State = EntityState.Modified;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; catch (DbUpdateConcurrencyException)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!SuppliersExists(id))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NoContent();
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // POST: api/Supplier
&nbsp; &nbsp; &nbsp; &nbsp; [HttpPost]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> PostSuppliers([FromBody] Suppliers suppliers)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Suppliers.Add(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return CreatedAtAction("GetSuppliers", new { id = suppliers.SupplierId }, suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; // DELETE: api/Supplier/5
&nbsp; &nbsp; &nbsp; &nbsp; [HttpDelete("{id}")]
&nbsp; &nbsp; &nbsp; &nbsp; public async Task<IActionResult> DeleteSuppliers([FromRoute] int id)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var suppliers = await _context.Suppliers.FindAsync(id);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (suppliers == null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _context.Suppliers.Remove(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; await _context.SaveChangesAsync();

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Ok(suppliers);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; private bool SuppliersExists(int id)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return _context.Suppliers.Any(e => e.SupplierId == id);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

As you see that generated Controllers using original fields from the Suppliers Model. Next, we will use our previous created DTO for custom Object save, edit and their response. Open and edit Controllers/SupplierController.cs then add this variable after context variable.

private readonly IMapper _mapper;
private readonly IDataRepository<Suppliers> _repo;

Also, inject to the SupplierController constructor.

public SupplierController(NORTHWNDContext context, IMapper mapper, IDataRepository<Suppliers> repo)
{
&nbsp; &nbsp; _context = context;
&nbsp; &nbsp; _mapper = mapper;
&nbsp; &nbsp; _repo = repo;
}

Don’t forget to import all required libraries or classes. Next, replace the HTTPPost method with this codes.

// POST: api/Supplier
[HttpPost]
public async Task<IActionResult> PostSuppliers([FromBody] AddSupplierDto addSupplierDto)
{
&nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; }

&nbsp; &nbsp; var preSupplier = _mapper.Map<Suppliers>(addSupplierDto);
&nbsp; &nbsp; _repo.Add(preSupplier);
&nbsp; &nbsp; var saveSupplier = await _repo.SaveAsync(preSupplier);
&nbsp; &nbsp; var supplierResponse = _mapper.Map<SupplierResponseDto>(saveSupplier);

&nbsp; &nbsp; return StatusCode(201, new { supplierResponse });
}

Next, replace the HTTPPut method with this codes.

// PUT: api/Supplier/5
[HttpPut("{id}")]
public async Task<IActionResult> PutSuppliers([FromRoute] int id, [FromBody] EditSupplierDto editSupplierDto)
{
&nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; }

&nbsp; &nbsp; if (id != editSupplierDto.SupplierId)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest();
&nbsp; &nbsp; }

&nbsp; &nbsp; var preSupplier = _mapper.Map<Suppliers>(editSupplierDto);
&nbsp; &nbsp; _repo.Update(preSupplier);
&nbsp; &nbsp; await _repo.SaveAsync(preSupplier);

&nbsp; &nbsp; return NoContent();
}

Finally, replace the HTTPDelete method with this because we will delete manually busing SQL Query the related table.

// DELETE: api/Supplier/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteSuppliers([FromRoute] int id)
{
&nbsp; &nbsp; if (!ModelState.IsValid)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return BadRequest(ModelState);
&nbsp; &nbsp; }

&nbsp; &nbsp; var suppliers = await _context.Suppliers.FindAsync(id);

&nbsp; &nbsp; if (suppliers == null)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; return NotFound();
&nbsp; &nbsp; }

&nbsp; &nbsp; _context.Database.ExecuteSqlCommand("DELETE FROM [Order Details] WHERE ProductID IN (SELECT ProductID FROM Products WHERE SupplierID = @supplierId)",
&nbsp; &nbsp; &nbsp; &nbsp; new SqlParameter("@supplierId", suppliers.SupplierId));

&nbsp; &nbsp; _context.Database.ExecuteSqlCommand("DELETE FROM Products WHERE SupplierID = @supplierId",
&nbsp; &nbsp; &nbsp; &nbsp; new SqlParameter("@supplierId", suppliers.SupplierId));

&nbsp; &nbsp; _context.Suppliers.Remove(suppliers);
&nbsp; &nbsp; await _context.SaveChangesAsync();

&nbsp; &nbsp; return Ok(suppliers);
}

8. Test API using Postman

Now, we have to run the ASP.NET Core Web API from the Terminal by typing this command.

dotnet watch run

Watch keyword is the additional command for monitoring any change in the codes then reloading the ASP.NET Core Web API application. Next, open or run the Postman application. Use the GET method and fill the right column after the GET method with localhost:5000/api/Supplier, Headers key with Content-Type, Headers value with application/json.

Now, click the Send button and you should see this result for successful GET Supplier.

[
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "supplierId": 1,
&nbsp; &nbsp; &nbsp; &nbsp; "companyName": "Exotic Liquids",
&nbsp; &nbsp; &nbsp; &nbsp; "contactName": "Charlotte Cooper",
&nbsp; &nbsp; &nbsp; &nbsp; "contactTitle": "Purchasing Manager",
&nbsp; &nbsp; &nbsp; &nbsp; "address": "49 Gilbert St.",
&nbsp; &nbsp; &nbsp; &nbsp; "city": "London",
&nbsp; &nbsp; &nbsp; &nbsp; "region": null,
&nbsp; &nbsp; &nbsp; &nbsp; "postalCode": "EC1 4SD",
&nbsp; &nbsp; &nbsp; &nbsp; "country": "UK",
&nbsp; &nbsp; &nbsp; &nbsp; "phone": "(171) 555-2222",
&nbsp; &nbsp; &nbsp; &nbsp; "fax": null,
&nbsp; &nbsp; &nbsp; &nbsp; "homePage": null,
&nbsp; &nbsp; &nbsp; &nbsp; "products": []
&nbsp; &nbsp; },
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; "supplierId": 2,
&nbsp; &nbsp; &nbsp; &nbsp; "companyName": "New Orleans Cajun Delights",
&nbsp; &nbsp; &nbsp; &nbsp; "contactName": "Shelley Burke",
&nbsp; &nbsp; &nbsp; &nbsp; "contactTitle": "Order Administrator",
&nbsp; &nbsp; &nbsp; &nbsp; "address": "P.O. Box 78934",
&nbsp; &nbsp; &nbsp; &nbsp; "city": "New Orleans",
&nbsp; &nbsp; &nbsp; &nbsp; "region": "LA",
&nbsp; &nbsp; &nbsp; &nbsp; "postalCode": "70117",
&nbsp; &nbsp; &nbsp; &nbsp; "country": "USA",
&nbsp; &nbsp; &nbsp; &nbsp; "phone": "(100) 555-4822",
&nbsp; &nbsp; &nbsp; &nbsp; "fax": null,
&nbsp; &nbsp; &nbsp; &nbsp; "homePage": "#CAJUN.HTM#",
&nbsp; &nbsp; &nbsp; &nbsp; "products": []
&nbsp; &nbsp; },
&nbsp; &nbsp; ...
]

To get single supplier by supplier ID, just change the URL to this localhost:5000/api/Supplier/2. If found, you will see the result of a single supplier. Next, for POST a Supplier changes the method to POST, leave URL same as the GET supplier list then fill the body with a raw JSON object.

{
&nbsp; "CompanyName": "Djamware Inc.",
&nbsp; "ContactName": "Didin J.",
&nbsp; "ContactTitle": "CEO",
&nbsp; "Address": "Whereever Road 123",
&nbsp; "City": "Bandung",
&nbsp; "Region": "JBR",
&nbsp; "PostalCode": "12345",
&nbsp; "Country": "Indonesia",
&nbsp; "Phone": "(022) 123-4567890",
&nbsp; "Fax": "(022) 123-4567890",
&nbsp; "HomePage": "https://www.djamware.com"
}

You will see this result with status 201.

{
&nbsp; &nbsp; "supplierResponse": {
&nbsp; &nbsp; &nbsp; &nbsp; "companyName": "Djamware Inc.",
&nbsp; &nbsp; &nbsp; &nbsp; "contactName": "Didin J.",
&nbsp; &nbsp; &nbsp; &nbsp; "contactTitle": "CEO",
&nbsp; &nbsp; &nbsp; &nbsp; "address": "Whereever Road 123",
&nbsp; &nbsp; &nbsp; &nbsp; "city": "Bandung",
&nbsp; &nbsp; &nbsp; &nbsp; "region": "JBR",
&nbsp; &nbsp; &nbsp; &nbsp; "postalCode": "12345",
&nbsp; &nbsp; &nbsp; &nbsp; "country": "Indonesia",
&nbsp; &nbsp; &nbsp; &nbsp; "phone": "(022) 123-4567890",
&nbsp; &nbsp; &nbsp; &nbsp; "fax": "(022) 123-4567890",
&nbsp; &nbsp; &nbsp; &nbsp; "homePage": "https://www.djamware.com"
&nbsp; &nbsp; }
}

To update the Supplier data, change the method to PUT and URL to localhost:5000/api/Supplier/30 then add a supplier id field to the raw body.

{
&nbsp; "SupplierId": 30,
&nbsp; "CompanyName": "Djamware.com",
&nbsp; "ContactName": "Didin J.",
&nbsp; "ContactTitle": "Engineer",
&nbsp; "Address": "Whereever Road 123",
&nbsp; "City": "Garut",
&nbsp; "Region": "JBR",
&nbsp; "PostalCode": "12345",
&nbsp; "Country": "Indonesia",
&nbsp; "Phone": "(022) 123-4567890",
&nbsp; "Fax": "(022) 123-4567890",
&nbsp; "HomePage": "https://www.djamware.com"
}

The response should be 204 (No Content) for a successful request. Next, for delete a supplier, simply change the method to DELETE, keep the URL as the previous method and change the body to none. You should see the 200 response and data that deleted in the response body.

9. Install or Update Angular 7 CLI and Create Application

Before installing the Angular 7 CLI, make sure you have installed Node.js https://nodejs.org and can open Node.js command prompt. Next, open the Node.js command prompt then type this command to install Angular 7 CLI.

npm install -g @angular/cli

Next, create an Angular 7 application by typing this command in the root of ASP.NET Core application/project directory.

ng new client

Where client is the name of the Angular 7 application. You can specify your own name, we like to name it client because it’s put inside ASP.NET Core Project directory. If there’s a question, we fill them with Y and SASS. Next, go to the newly created Angular 7 application.

cd client

Run the Angular 7 application for the first time.

ng serve

Now, go to localhost:4200 and you should see this page.

To stop the Angular 7 application, just press CTRL+C keys.

10. Add Routes for Navigation between Angular 7 Pages/Component

On the previous steps, we have to added Angular 7 Routes when answering the questions. Now, we just added the required pages for CRUD (Create, Read, Update, Delete) Supplier data. Type this commands to add the Angular 7 components or pages.

ng g component supplier
ng g component supplier-detail
ng g component supplier-add
ng g component supplier-edit

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add this imports.

import { SupplierComponent } from './supplier/supplier.component';
import { SupplierDetailComponent } from './supplier-detail/supplier-detail.component';
import { SupplierAddComponent } from './supplier-add/supplier-add.component';
import { SupplierEditComponent } from './supplier-edit/supplier-edit.component';

Add these arrays to the existing routes constant.

const routes: Routes = [
&nbsp; {
&nbsp; &nbsp; path: 'supplier',
&nbsp; &nbsp; component: SupplierComponent,
&nbsp; &nbsp; data: { title: 'List of Suppliers' }
&nbsp; },
&nbsp; {
&nbsp; &nbsp; path: 'supplier-details/:id',
&nbsp; &nbsp; component: SupplierDetailComponent,
&nbsp; &nbsp; data: { title: 'Supplier Details' }
&nbsp; },
&nbsp; {
&nbsp; &nbsp; path: 'supplier-add',
&nbsp; &nbsp; component: SupplierAddComponent,
&nbsp; &nbsp; data: { title: 'Add Supplier' }
&nbsp; },
&nbsp; {
&nbsp; &nbsp; path: 'supplier-edit/:id',
&nbsp; &nbsp; component: SupplierEditComponent,
&nbsp; &nbsp; data: { title: 'Edit Supplier' }
&nbsp; },
&nbsp; { path: '',
&nbsp; &nbsp; redirectTo: '/supplier',
&nbsp; &nbsp; pathMatch: 'full'
&nbsp; }
];

Open and edit src/app/app.component.html and you will see existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div style="text-align:center">
&nbsp; <h1>
&nbsp; &nbsp; Welcome to {{ title }}!
&nbsp; </h1>
&nbsp; <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>

<div class="container">
&nbsp; <router-outlet></router-outlet>
</div>

Open and edit src/app/app.component.scss then replace all SCSS codes with this.

.container {
&nbsp; padding: 20px;
}

11. Create Service for Accessing RESTful API

To access ASP.NET Core Web API from Angular 7 application, we have to create an Angular 7 Service first. Type this command to create it.

ng g service api

We have to register the HttpClient module before using it in the Service. Open and edit src/app/app.module.ts then add this import.

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add them to the `@NgModule` imports array.

imports: [
&nbsp; BrowserModule,
&nbsp; FormsModule,
&nbsp; HttpClientModule,
&nbsp; AppRoutingModule
],

To specify the type of response object from the ASP.NET Core Web API, we have to create a class for it. Create a new file src/app/supplier.ts then add this codes.

export class Supplier {
&nbsp; &nbsp; supplierId: number;
&nbsp; &nbsp; companyName: string;
&nbsp; &nbsp; contactName: string;
&nbsp; &nbsp; contactTitle: string;
&nbsp; &nbsp; address: string;
&nbsp; &nbsp; city: string;
&nbsp; &nbsp; region: string;
&nbsp; &nbsp; postalCode: string;
&nbsp; &nbsp; country: string;
&nbsp; &nbsp; phone: string;
&nbsp; &nbsp; fax: string;
&nbsp; &nbsp; homePage: string;
}

Next, open and edit src/app/api.service.ts then add this imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Supplier } from './supplier';

Add these constants before the @Injectable.

const httpOptions = {
&nbsp; headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = 'http://localhost:5000/api/';

Inject HttpClient module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

private handleError<T> (operation = 'operation', result?: T) {
&nbsp; return (error: any): Observable<T> => {

&nbsp; &nbsp; // TODO: send the error to remote logging infrastructure
&nbsp; &nbsp; console.error(error); // log to console instead

&nbsp; &nbsp; // Let the app keep running by returning an empty result.
&nbsp; &nbsp; return of(result as T);
&nbsp; };
}

Add all CRUD (create, read, update, delete) functions of suppliers data.

getSuppliers (): Observable<Supplier[]> {
&nbsp; return this.http.get<Supplier[]>(apiUrl)
&nbsp; &nbsp; .pipe(
&nbsp; &nbsp; &nbsp; tap(heroes => console.log('fetched Suppliers')),
&nbsp; &nbsp; &nbsp; catchError(this.handleError('getSuppliers', []))
&nbsp; &nbsp; );
}

getSupplier(id: number): Observable<Supplier> {
&nbsp; const url = `${apiUrl}/${id}`;
&nbsp; return this.http.get<Supplier>(url).pipe(
&nbsp; &nbsp; tap(_ => console.log(`fetched Supplier id=${id}`)),
&nbsp; &nbsp; catchError(this.handleError<Supplier>(`getSupplier id=${id}`))
&nbsp; );
}

addSupplier (supplier: any): Observable<Supplier> {
&nbsp; return this.http.post<Supplier>(apiUrl, supplier, httpOptions).pipe(
&nbsp; &nbsp; tap((supplierRes: Supplier) => console.log(`added Supplier w/ id=${supplierRes.supplierId}`)),
&nbsp; &nbsp; catchError(this.handleError<Supplier>('addSupplier'))
&nbsp; );
}

updateSupplier (id: number, supplier: any): Observable<any> {
&nbsp; const url = `${apiUrl}/${id}`;
&nbsp; return this.http.put(url, supplier, httpOptions).pipe(
&nbsp; &nbsp; tap(_ => console.log(`updated Supplier id=${id}`)),
&nbsp; &nbsp; catchError(this.handleError<any>('updateSupplier'))
&nbsp; );
}

deleteSupplier (id: number): Observable<Supplier> {
&nbsp; const url = `${apiUrl}/${id}`;
&nbsp; return this.http.delete<Supplier>(url, httpOptions).pipe(
&nbsp; &nbsp; tap(_ => console.log(`deleted Supplier id=${id}`)),
&nbsp; &nbsp; catchError(this.handleError<Supplier>('deleteSupplier'))
&nbsp; );
}

12. Display List of Suppliers using Angular 7 Material

To display a list of suppliers to the Angular 7 template. First, open and edit src/app/supplier/supplier.component.ts then add this imports.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for user interface (UI) we will use Angular 7 Material and CDK. There’s a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 7 Material.

ng add @angular/material

If there are some questions, answer them like below.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green &nbsp; &nbsp; &nbsp; [ Preview: https://material.angular.i
o?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, we have to register all required Angular Material components or modules to src/app/app.module.ts. Open and edit that file then add this imports.

import {
&nbsp; MatInputModule,
&nbsp; MatPaginatorModule,
&nbsp; MatProgressSpinnerModule,
&nbsp; MatSortModule,
&nbsp; MatTableModule,
&nbsp; MatIconModule,
&nbsp; MatButtonModule,
&nbsp; MatCardModule,
&nbsp; MatFormFieldModule } from '@angular/material';

Also, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
&nbsp; BrowserModule,
&nbsp; FormsModule,
&nbsp; HttpClientModule,
&nbsp; AppRoutingModule,
&nbsp; ReactiveFormsModule,
&nbsp; BrowserAnimationsModule,
&nbsp; MatInputModule,
&nbsp; MatTableModule,
&nbsp; MatPaginatorModule,
&nbsp; MatSortModule,
&nbsp; MatProgressSpinnerModule,
&nbsp; MatIconModule,
&nbsp; MatButtonModule,
&nbsp; MatCardModule,
&nbsp; MatFormFieldModule
],

Next, back to src/app/supplier/supplier.component.ts then add this imports.

import { Supplier } from '../supplier';

Declare the variables of Angular Material Table Data Source before the constructor.

displayedColumns: string[] = ['supplierId', 'companyName', 'contactName'];
data: Supplier[] = [];
isLoadingResults = true;

Modify the ngOnInit function to get list of suppliers immediately.

ngOnInit() {
&nbsp; this.api.getSuppliers()
&nbsp; .subscribe(res => {
&nbsp; &nbsp; this.data = res;
&nbsp; &nbsp; console.log(this.data);
&nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; }, err => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; });
}

Next, open and edit src/app/supplier/supplier.component.html then replace all HTML tags with this Angular 7 Material tags.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier-add']"><mat-icon>add</mat-icon></a>
&nbsp; </div>
&nbsp; <div class="mat-elevation-z8">
&nbsp; &nbsp; <table mat-table [dataSource]="data" class="example-table"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;matSort matSortActive="CompanyName" matSortDisableClear matSortDirection="asc">

&nbsp; &nbsp; &nbsp; <!-- Supplier ID Column -->
&nbsp; &nbsp; &nbsp; <ng-container matColumnDef="supplierId">
&nbsp; &nbsp; &nbsp; &nbsp; <th mat-header-cell *matHeaderCellDef>Supplier ID</th>
&nbsp; &nbsp; &nbsp; &nbsp; <td mat-cell *matCellDef="let row">{{row.supplierId}}</td>
&nbsp; &nbsp; &nbsp; </ng-container>

&nbsp; &nbsp; &nbsp; <!-- Company Name Column -->
&nbsp; &nbsp; &nbsp; <ng-container matColumnDef="companyName">
&nbsp; &nbsp; &nbsp; &nbsp; <th mat-header-cell *matHeaderCellDef>Company Name</th>
&nbsp; &nbsp; &nbsp; &nbsp; <td mat-cell *matCellDef="let row">{{row.companyName}}</td>
&nbsp; &nbsp; &nbsp; </ng-container>

&nbsp; &nbsp; &nbsp; <!-- Contact Name Column -->
&nbsp; &nbsp; &nbsp; <ng-container matColumnDef="contactName">
&nbsp; &nbsp; &nbsp; &nbsp; <th mat-header-cell *matHeaderCellDef>Contact Name</th>
&nbsp; &nbsp; &nbsp; &nbsp; <td mat-cell *matCellDef="let row">{{row.contactName}}</td>
&nbsp; &nbsp; &nbsp; </ng-container>

&nbsp; &nbsp; &nbsp; <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
&nbsp; &nbsp; &nbsp; <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/supplier-details/', row.supplierId]"></tr>
&nbsp; &nbsp; </table>
&nbsp; </div>
</div>

Finally, to make a little UI adjustment, open and edit src/app/supplier/supplier.component.css then add this CSS codes.

/* Structure */
.example-container {
&nbsp; &nbsp; position: relative;
&nbsp; &nbsp; padding: 5px;
&nbsp; }

&nbsp; .example-table-container {
&nbsp; &nbsp; position: relative;
&nbsp; &nbsp; max-height: 400px;
&nbsp; &nbsp; overflow: auto;
&nbsp; }

&nbsp; table {
&nbsp; &nbsp; width: 100%;
&nbsp; }

&nbsp; .example-loading-shade {
&nbsp; &nbsp; position: absolute;
&nbsp; &nbsp; top: 0;
&nbsp; &nbsp; left: 0;
&nbsp; &nbsp; bottom: 56px;
&nbsp; &nbsp; right: 0;
&nbsp; &nbsp; background: rgba(0, 0, 0, 0.15);
&nbsp; &nbsp; z-index: 1;
&nbsp; &nbsp; display: flex;
&nbsp; &nbsp; align-items: center;
&nbsp; &nbsp; justify-content: center;
&nbsp; }

&nbsp; .example-rate-limit-reached {
&nbsp; &nbsp; color: #980000;
&nbsp; &nbsp; max-width: 360px;
&nbsp; &nbsp; text-align: center;
&nbsp; }

&nbsp; /* Column Widths */
&nbsp; .mat-column-number,
&nbsp; .mat-column-state {
&nbsp; &nbsp; max-width: 64px;
&nbsp; }

&nbsp; .mat-column-created {
&nbsp; &nbsp; max-width: 124px;
&nbsp; }

&nbsp; .mat-flat-button {
&nbsp; &nbsp; margin: 5px;
&nbsp; }

13. Show and Delete Supplier Details using Angular 7 Material

To show supplier details after click or tap on the one of a row inside the Angular 7 Material table, open and edit src/app/supplier-detail/supplier-detail.component.ts then add this imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Supplier } from '../supplier';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold supplier data that get from the API.

supplier: Supplier = {
&nbsp; supplierId: null,
&nbsp; companyName: '',
&nbsp; contactName: '',
&nbsp; contactTitle: '',
&nbsp; address: '',
&nbsp; city: '',
&nbsp; region: '',
&nbsp; postalCode: '',
&nbsp; country: '',
&nbsp; phone: '',
&nbsp; fax: '',
&nbsp; homePage: ''
};
isLoadingResults = true;

Add a function for getting Supplier data from the API.

getSupplierDetails(id) {
&nbsp; this.api.getSupplier(id)
&nbsp; &nbsp; .subscribe(data => {
&nbsp; &nbsp; &nbsp; this.supplier = data;
&nbsp; &nbsp; &nbsp; console.log(this.supplier);
&nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; });
}

Call that function when the component is initiated.

ngOnInit() {
&nbsp; this.getSupplierDetails(this.route.snapshot.params['id']);
}

Add this function for delete supplier.

deleteSupplier(id: number) {
&nbsp; this.isLoadingResults = true;
&nbsp; this.api.deleteSupplier(id)
&nbsp; &nbsp; .subscribe(res => {
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; &nbsp; this.router.navigate(['/supplier']);
&nbsp; &nbsp; &nbsp; }, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; );
}

For the view, open and edit src/app/supplier-detail/supplier-detail.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier']"><mat-icon>list</mat-icon></a>
&nbsp; </div>
&nbsp; <mat-card class="example-card">
&nbsp; &nbsp; <mat-card-header>
&nbsp; &nbsp; &nbsp; <mat-card-title><h2>Supplier ID: {{supplier.supplierId}}</h2></mat-card-title>
&nbsp; &nbsp; &nbsp; <mat-card-subtitle>Company Name: {{supplier.companyName}}</mat-card-subtitle>
&nbsp; &nbsp; </mat-card-header>
&nbsp; &nbsp; <mat-card-content>
&nbsp; &nbsp; &nbsp; <dl>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Contact Name:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.contactName}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Contact Title:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.contactTitle}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Address:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.address}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>City:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.city}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Region:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.region}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Postal Code:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.postalCode}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Country:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.country}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Phone:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.phone}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Fax:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.fax}}</dd>
&nbsp; &nbsp; &nbsp; &nbsp; <dt>Home Page:</dt>
&nbsp; &nbsp; &nbsp; &nbsp; <dd>{{supplier.homePage}}</dd>
&nbsp; &nbsp; &nbsp; </dl>
&nbsp; &nbsp; </mat-card-content>
&nbsp; &nbsp; <mat-card-actions>
&nbsp; &nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier-edit/', supplier.supplierId || 'all']"><mat-icon>edit</mat-icon></a>
&nbsp; &nbsp; &nbsp; <a mat-flat-button color="warn" (click)="deleteSupplier(supplier.supplierId)"><mat-icon>delete</mat-icon></a>
&nbsp; &nbsp; </mat-card-actions>
&nbsp; </mat-card>
</div>

Finally, open and edit src/app/supplier-detail/supplier-detail.component.css then add these lines of CSS codes.

/* Structure */
.example-container {
&nbsp; position: relative;
&nbsp; padding: 5px;
}

.example-loading-shade {
&nbsp; position: absolute;
&nbsp; top: 0;
&nbsp; left: 0;
&nbsp; bottom: 56px;
&nbsp; right: 0;
&nbsp; background: rgba(0, 0, 0, 0.15);
&nbsp; z-index: 1;
&nbsp; display: flex;
&nbsp; align-items: center;
&nbsp; justify-content: center;
}

.mat-flat-button {
&nbsp; margin: 5px;
}

14. Add a Supplier using Angular 7 Material

To add a new supplier, we have to create Angular 7 reactive form. Open and edit src/app/supplier-add/supplier-add.component.ts then add this imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

supplierForm: FormGroup;
companyName = '';
contactName = '';
contactTitle = '';
address = '';
city = '';
region = '';
postalCode = '';
country = '';
phone = '';
fax = '';
homePage = '';
isLoadingResults = false;

Add initial validation for each field.

ngOnInit() {
&nbsp; this.supplierForm = this.formBuilder.group({
&nbsp; &nbsp; 'companyName' : [null, Validators.required],
&nbsp; &nbsp; 'contactName' : [null, null],
&nbsp; &nbsp; 'contactTitle' : [null, null],
&nbsp; &nbsp; 'address' : [null, null],
&nbsp; &nbsp; 'city' : [null, null],
&nbsp; &nbsp; 'region' : [null, null],
&nbsp; &nbsp; 'postalCode' : [null, null],
&nbsp; &nbsp; 'country' : [null, null],
&nbsp; &nbsp; 'phone' : [null, null],
&nbsp; &nbsp; 'fax' : [null, null],
&nbsp; &nbsp; 'homePage' : [null, null]
&nbsp; });
}

Create a function for submitting or POST supplier form.

onFormSubmit(form: NgForm) {
&nbsp; this.isLoadingResults = true;
&nbsp; this.api.addSupplier(form)
&nbsp; &nbsp; .subscribe((res: { [x: string]: any; }) => {
&nbsp; &nbsp; &nbsp; &nbsp; const supplier = res['supplierResponse'];
&nbsp; &nbsp; &nbsp; &nbsp; const id = supplier['supplierId'];
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; &nbsp; this.router.navigate(['/supplier-details', id]);
&nbsp; &nbsp; &nbsp; }, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; });
}

Next, add this import for implementing ErrorStateMatcher.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class after the end of this class bracket.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
&nbsp; isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
&nbsp; &nbsp; const isSubmitted = form && form.submitted;
&nbsp; &nbsp; return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
&nbsp; }
}

Instantiate that MyErrorStateMatcher as a variable in main class.

matcher = new MyErrorStateMatcher();

Next, open and edit src/app/supplier-add/supplier-add.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier']"><mat-icon>list</mat-icon></a>
&nbsp; </div>
&nbsp; <mat-card class="example-card">
&nbsp; &nbsp; <form [formGroup]="supplierForm" (ngSubmit)="onFormSubmit(supplierForm.value)">
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Company Name" formControlName="companyName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; &nbsp; <mat-error>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span *ngIf="!supplierForm.get('companyName').valid && supplierForm.get('companyName').touched">Please enter Company Name</span>
&nbsp; &nbsp; &nbsp; &nbsp; </mat-error>
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Name" formControlName="contactName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Title" formControlName="contactTitle"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Address" formControlName="address"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="City" formControlName="city"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Region" formControlName="region"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Postal Code" formControlName="postalCode"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Country" formControlName="country"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Phone" formControlName="phone"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Fax" formControlName="fax"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Home Page" formControlName="homePage"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <div class="button-row">
&nbsp; &nbsp; &nbsp; &nbsp; <button type="submit" [disabled]="!supplierForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
&nbsp; &nbsp; &nbsp; </div>
&nbsp; &nbsp; </form>
&nbsp; </mat-card>
</div>

Finally, open and edit src/app/supplier-add/supplier-add.component.css then add this CSS codes.

/* Structure */
.example-container {
&nbsp; position: relative;
&nbsp; padding: 5px;
}

.example-form {
&nbsp; min-width: 150px;
&nbsp; max-width: 500px;
&nbsp; width: 100%;
}

.example-full-width {
&nbsp; width: 100%;
}

.example-full-width:nth-last-child() {
&nbsp; margin-bottom: 10px;
}

.button-row {
&nbsp; margin: 10px 0;
}

.mat-flat-button {
&nbsp; margin: 5px;
}

15. Edit a Supplier using Angular 7 Material

We have put an edit button inside the Supplier Detail component for call Edit page. Now, open and edit src/app/supplier-edit/supplier-edit.component.ts then add this imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Inject above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the supplier form before the constructor.

supplierForm: FormGroup;
companyName = '';
contactName = '';
contactTitle = '';
address = '';
city = '';
region = '';
postalCode = '';
country = '';
phone = '';
fax = '';
homePage = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Next, add validation for all fields when the component is initiated.

ngOnInit() {
&nbsp; this.getSupplier(this.route.snapshot.params['id']);
&nbsp; this.supplierForm = this.formBuilder.group({
&nbsp; &nbsp; 'companyName' : [null, Validators.required],
&nbsp; &nbsp; 'contactName' : [null, null],
&nbsp; &nbsp; 'contactTitle' : [null, null],
&nbsp; &nbsp; 'address' : [null, null],
&nbsp; &nbsp; 'city' : [null, null],
&nbsp; &nbsp; 'region' : [null, null],
&nbsp; &nbsp; 'postalCode' : [null, null],
&nbsp; &nbsp; 'country' : [null, null],
&nbsp; &nbsp; 'phone' : [null, null],
&nbsp; &nbsp; 'fax' : [null, null],
&nbsp; &nbsp; 'homePage' : [null, null]
&nbsp; });
}

Create a function for getting supplier data that filled to each form fields.

getSupplier(id: number) {
&nbsp; this.api.getSupplier(id).subscribe(data => {
&nbsp; &nbsp; this.supplierId = data.supplierId;
&nbsp; &nbsp; this.supplierForm.setValue({
&nbsp; &nbsp; &nbsp; companyName: data.companyName,
&nbsp; &nbsp; &nbsp; contactName: data.contactName,
&nbsp; &nbsp; &nbsp; contactTitle: data.contactTitle,
&nbsp; &nbsp; &nbsp; address: data.address,
&nbsp; &nbsp; &nbsp; city: data.city,
&nbsp; &nbsp; &nbsp; region: data.region,
&nbsp; &nbsp; &nbsp; postalCode: data.postalCode,
&nbsp; &nbsp; &nbsp; country: data.country,
&nbsp; &nbsp; &nbsp; phone: data.phone,
&nbsp; &nbsp; &nbsp; fax: data.fax,
&nbsp; &nbsp; &nbsp; homePage: data.homePage
&nbsp; &nbsp; });
&nbsp; });
}

Create a function to update the supplier changes.

onFormSubmit(form: NgForm) {
&nbsp; this.isLoadingResults = true;
&nbsp; this.api.updateSupplier(this.supplierId, form)
&nbsp; &nbsp; .subscribe(res => {
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; &nbsp; this.router.navigate(['/supplier']);
&nbsp; &nbsp; &nbsp; }, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; this.isLoadingResults = false;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; );
}

Add a function for handling show supplier details button.

supplierDetails() {
&nbsp; this.router.navigate(['/supplier-details', this.supplierId]);
}

Add a class that implementing ErrorStateMatcher after the current class.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
&nbsp; isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
&nbsp; &nbsp; const isSubmitted = form && form.submitted;
&nbsp; &nbsp; return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
&nbsp; }
}

Next, open and edit src/app/supplier-edit/supplier-edit.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
&nbsp; <div class="example-loading-shade"
&nbsp; &nbsp; &nbsp; &nbsp;*ngIf="isLoadingResults">
&nbsp; &nbsp; <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
&nbsp; </div>
&nbsp; <div class="button-row">
&nbsp; &nbsp; <a mat-flat-button color="primary" [routerLink]="['/supplier']"><mat-icon>list</mat-icon></a>
&nbsp; </div>
&nbsp; <mat-card class="example-card">
&nbsp; &nbsp; <form [formGroup]="supplierForm" (ngSubmit)="onFormSubmit(supplierForm.value)">
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Company Name" formControlName="companyName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; &nbsp; <mat-error>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span *ngIf="!supplierForm.get('companyName').valid && supplierForm.get('companyName').touched">Please enter Company Name</span>
&nbsp; &nbsp; &nbsp; &nbsp; </mat-error>
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Name" formControlName="contactName"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Contact Title" formControlName="contactTitle"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Address" formControlName="address"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="City" formControlName="city"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Region" formControlName="region"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Postal Code" formControlName="postalCode"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Country" formControlName="country"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Phone" formControlName="phone"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Fax" formControlName="fax"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <mat-form-field class="example-full-width">
&nbsp; &nbsp; &nbsp; &nbsp; <input matInput placeholder="Home Page" formControlName="homePage"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[errorStateMatcher]="matcher">
&nbsp; &nbsp; &nbsp; </mat-form-field>
&nbsp; &nbsp; &nbsp; <div class="button-row">
&nbsp; &nbsp; &nbsp; &nbsp; <button type="submit" [disabled]="!supplierForm.valid" mat-flat-button color="primary"><mat-icon>update</mat-icon></button>
&nbsp; &nbsp; &nbsp; </div>
&nbsp; &nbsp; </form>
&nbsp; </mat-card>
</div>

Finally, open and edit src/app/supplier-edit/supplier-edit.component.css then add this lines of CSS codes.

/* Structure */
.example-container {
&nbsp; position: relative;
&nbsp; padding: 5px;
}

.example-form {
&nbsp; min-width: 150px;
&nbsp; max-width: 500px;
&nbsp; width: 100%;
}

.example-full-width {
&nbsp; width: 100%;
}

.example-full-width:nth-last-child() {
&nbsp; margin-bottom: 10px;
}

.button-row {
&nbsp; margin: 10px 0;
}

.mat-flat-button {
&nbsp; margin: 5px;
}

16. Run and Test the ASP.NET Core Web API and Angular 7 CRUD Web Application

Before running the ASP.NET Core Web API that consumes by Angular 7 application, make sure ASP.NET Core Web API serve by HTTP only. Just open Properties\launchSettings.json then remove https from the applicationUrl. Next, type this command in the Visual Studio Code Terminal.

dotnet watch run

To run the Angular 7 application, type this command from the Node.js Command Prompt.

ng serve

Now, you will see this Angular 7 pages when pointing your browser to localhost:4200.

You can just navigate through the whole Angular 7 application.

That it’s, the tutorial of Building Web App using ASP.NET Web API Angular 7 and SQL Server. You can find the fully working source code from our GitHub.

Learn More

Angular 7 (formerly Angular 2) - The Complete Guide

Learn and Understand AngularJS

Angular Crash Course for Busy Developers

The Complete Angular Course: Beginner to Advanced

Angular (Angular 2+) & NodeJS - The MEAN Stack Guide

The Complete ASP.NET MVC 5 Course

Build a Real-world App with ASP.NET Core and Angular 2 (4+)

ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course