Hướng dẫn asp.net core 2.0 mvc

TL;DR: ASP.NET Core, the rewritten, cross-platform, and open source version of ASP.NET framework is gaining popularity for being easy to use and for having great performance when compared to modern solutions like Java, Go and Node.js. In this article we are going to use ASP.NET Core to create a simple RESTful API that handles grocery lists and then we are going to add authentication to secure this API.

"Creating secure RESTful APIs with ASP.NET Core is a piece of cake."

Tweet This

What is ASP.NET Core

ASP.NET Core is an open source redesign of the popular ASP.NET framework. This new version was developed to support modern cloud based applications, such as web applications, Internet of Things (IoT) devices, and mobile backends. There are a few differences between ASP.NET Core and its predecessor, the first big one is that the new version is cross-platform and can run on Windows, Mac and Linux. Another big difference is that ASP.NET Core is fully open source and available on GitHub.

ASP.NET Core vs ASP.NET Framework

As mentioned, ASP.NET Core is a new framework and, as such, it has much less support and libraries available than its predecessor. Microsoft has written a good article where it exposes when to use the new framework and when to use the old one. Basically, it says that developers should keep using the older version when they depend on third-party .NET libraries or NuGet packages that are not available for .NET Core, or when they are extending existing .NET applications.

This article also highlights that the following use cases are better fitted with .NET Core:

  • When cross-platform support is needed.
  • When microservices are the chosen architecture.
  • When the application will be dockerized (deployed on Docker containers).
  • When performance is a big priority (the article says that ASP.NET Core outperforms ASP.NET by a factor of 10).
    "ASP.NET Core outperforms ASP.NET by a factor of 10"

Tweet This

Installing .NET Core

The process to install .NET Core and to start developing applications depends on what platform we are going to use (i.e. Windows, Mac, or Linux). As I use Mac, I will show instructions on how to install it in this platform, but if you use or , please follow the instructions on Microsoft's web page before moving along.

.NET Core, on a Mac OS device, depends on the latest version of OpenSSL. To install it we will use Homebrew. If you already have Homebrew installed locally, just issue the following commands. If you don't, please install it first.

# updating brew references
brew update
# making sure openssl is already install
brew install openssl
# updating OpenSSL version
mkdir -p /usr/local/lib
ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/

After updating OpenSSL locally, you will have to download the .NET Core SDK and install it. Having the SDK installed, you can check if everything is in place by issuing the following command:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

Creating the ASP.NET Core App

To bootstrap an ASP.NET Core application, we are going to create a new folder called

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

8, and use

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

9 CLI (command line interface) to assemble the project structure. To do so, let's issue the following commands:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

This process, which shall take just a few seconds, will produce a structure that contains the following directories and files (a few resources were omitted for the sake of brevity):

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

During this article we are going to focus basically on the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

0 directory, where we will create RESTful controllers, and on the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

1 class, which is responsible for starting our API and configuring the services that we are going to use.

Creating the Grocery List API

Whenever a user wants to manage their grocery list, they will issue HTTP requests to our API. These HTTP requests are going to be handled by a new controller that we are going to create. To persist the grocery list items, this controller will interact with a database. Therefore we are going to need three new classes:

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

2, which will act as the persistence layer;

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

3, which will be the model that represents the items in our application; and

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

4, which will handle the HTTP requests issued by users.

Creating the Grocery Item Model

Our model will be quite simple, it will contain only an

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

5, which will be used to identify it, and a

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

6. Let's start by creating a

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

7 directory in the root path of our application, and then let's create a file called

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

8 on it. This new file will contain the following code:

namespace dotnet_grocery_list.Models
{
  public class GroceryItem
  {
    public long Id { get; set; }
    public string Description { get; set; }
  }
}

Adding a Persistence Layer

As mentioned, we will use an in-memory database to persist our grocery list's items. Therefore, let's start by adding the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

9 package to our project by issuing the following command:

dotnet add package Microsoft.EntityFrameworkCore.InMemory

After that we will create the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

2 class, which will handle the persistence features. This class will be created in the same

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

7 directory, and will have the following code:

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

Handling HTTP Requests

Handling HTTP requests with ASP.NET Core is a piece of cake. As we will see in the source code of the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

4 class, there are four attributes that we can add to methods that handle HTTP requests:

  • namespace dotnet_grocery_list.Models { public class GroceryItem {

    public long Id { get; set; }  
    public string Description { get; set; }  
    
    } }

    3 to handle HTTP GET requests.
  • namespace dotnet_grocery_list.Models { public class GroceryItem {

    public long Id { get; set; }  
    public string Description { get; set; }  
    
    } }

    4 to handle HTTP POST requests.
  • namespace dotnet_grocery_list.Models { public class GroceryItem {

    public long Id { get; set; }  
    public string Description { get; set; }  
    
    } }

    5 to handle HTTP DELETE requests.
  • namespace dotnet_grocery_list.Models { public class GroceryItem {

    public long Id { get; set; }  
    public string Description { get; set; }  
    
    } }

    6 to handle HTTP PUT requests.

We won't use the last attribute (

namespace dotnet_grocery_list.Models
{
  public class GroceryItem
  {
    public long Id { get; set; }
    public string Description { get; set; }
  }
}

  1. in this article, but it is useful when a method is supposed to handle updates in models. For example, if we were going to enable users to update a grocery item, we would add the

namespace dotnet_grocery_list.Models
{
  public class GroceryItem
  {
    public long Id { get; set; }
    public string Description { get; set; }
  }
}

6 attribute to the method responsible for the update.

Another useful attribute is

namespace dotnet_grocery_list.Models
{
  public class GroceryItem
  {
    public long Id { get; set; }
    public string Description { get; set; }
  }
}

9. This attribute is used to automatically deserialize method parameters from the body part of a HTTP request. In this article we are going to use this attribute to automatically transform a

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

3 from JSON to an object instance.

Now that we have a better understanding of what ASP.NET Core offers us, let's create the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

4 class. This class will be create in the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

2 file in the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

0 directory, and will contain the following source code:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

The last step that we need to perform to finish our (unauthenticated) grocery list application, is to configure the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

2 to use the in-memory database package that we have added to our project. To do so, let's open the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

1 file in the root directory of our application and change the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

6 method as follows:

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

Having everything in place, we can now use the RESTful API to save new grocery items to our list, get the whole list, and delete existing items through their ids. The following commands show how to interact with the API by using curl.

# get all items from the grocery list
curl http://localhost:5000/api/grocerylist
# add `buy some milk` to the list
curl -H "Content-Type: application/json" -X POST -d '{
    "description": "buy some milk"
}'  http://localhost:5000/api/grocerylist
# delete item with id 1
curl -X DELETE http://localhost:5000/api/grocerylist/1

The full source code of this application can be found in the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

7 branch of this GitHub repository.

Adding Authentication to ASP.NET Core

To secure our ASP.NET Core application, we are going to rely on JWTs (JSON Web Tokens). JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. The technology is becoming a de facto standard for securing microservices and backends for mobile applications and for SPA (Single Page Applications). Using JWTs to authenticate requests makes easier to create stateless applications and therefore to scale this applications horizontally. If you want to learn more about JWTs, take a look at this resource.

To secure our application, we are going to start by installing three packages:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

0

The first package, called JWT, will be used to issue JWTs to users signing in. The second one is the default package for handling Identity in ASP.NET Core applications, and will be integrated with our authentication solution. And the last package, JwtBearer, also provided by Microsoft, will be used to validate the tokens issued.

Configuring JWT Properties

To start integrating these packages in our application, we will create three properties in the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

8 file:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

1

The first property,

dotnet add package Microsoft.EntityFrameworkCore.InMemory

9, is used to sign the tokens that our application will create, and also to validate tokens received. The

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

0 and

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

1 properties will be included in all tokens that we issue, and when we receive a token we will assure that this token contains these two claims (

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

2 for

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

0 and

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

4 for

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

1). Like that we can guarantee that the token was indeed issued for our grocery list application.

To access these properties in our application, we are going to create a class called

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

6 in a new file in the root directory of our application:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

2

As we can see, this class has the exact same properties added to the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

8 file. To load the properties from the JSON file in an instance of this new class, add the following code as the first line of the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

6 method of the

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

9 class:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

3

Enabling User Registration

The next step is to create a class that will enable users to register in our application. This class will be created in a new file called

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

0 in the

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

1 directory, and will contain the following code:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

4

This class, for the time being, contains only one public method,

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

2, which accepts HTTP POST requests with

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

3 in the body. Besides this public method, it contains the following private methods:

  • using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    4—which is used to fill

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    5 (issued at),

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    6 (not before), and

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    7 (expiration) claims in the JWTs generated. These claims are used to validate if the token is indeed valid and are expected to contain a time represented in seconds passed after 1970.
  • using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    8 and

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    9—which are used to send error messages as JSON.
  • //... other using statements using dotnet_grocery_list.Models; using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list { public class Startup {

    //... everything else  
    public void ConfigureServices(IServiceCollection services)  
    {  
      // Configures GroceryListContext to use in-memory database  
      services.AddDbContext(opt => opt.UseInMemoryDatabase());  
      // Add framework services.  
      services.AddMvc();  
    }  
    
    } }

    0—a method that receives a set of key values pairs and generates a JWT adding all the values received alongside with the

    using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list.Models { public class GroceryListContext : DbContext {

    public GroceryListContext(DbContextOptions options)  
        : base(options)  
    {  
    }  
    public DbSet GroceryList { get; set; }  
    
    } }

    2,

    using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list.Models { public class GroceryListContext : DbContext {

    public GroceryListContext(DbContextOptions options)  
        : base(options)  
    {  
    }  
    public DbSet GroceryList { get; set; }  
    
    } }

    4,

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    6,

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    5, and

    using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using dotnet_grocery_list.Models; using System.Linq; namespace dotnet_grocery_list.Controllers { [Route("api/[controller]")] public class GroceryListController : Controller {

    private readonly GroceryListContext _context;  
    public GroceryListController(GroceryListContext context)  
    {  
      _context = context;  
      if (_context.GroceryList.Count() == 0)  
      {  
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });  
        _context.SaveChanges();  
      }  
    }  
    [HttpGet]  
    public IEnumerable GetAll()  
    {  
      return _context.GroceryList.ToList();  
    }  
    [HttpGet("{id}", Name = "GetGroceryItem")]  
    public IActionResult GetById(long id)  
    {  
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      return new ObjectResult(item);  
    }  
    [HttpPost]  
    public IActionResult Create([FromBody] GroceryItem item)  
    {  
      if (item == null)  
      {  
        return BadRequest();  
      }  
      _context.GroceryList.Add(item);  
      _context.SaveChanges();  
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);  
    }  
    [HttpDelete("{id}")]  
    public IActionResult Delete(long id)  
    {  
      var item = _context.GroceryList.First(t => t.Id == id);  
      if (item == null)  
      {  
        return NotFound();  
      }  
      _context.GroceryList.Remove(item);  
      _context.SaveChanges();  
      return new NoContentResult();  
    }  
    
    } }

    7 claims.
  • //... other using statements using dotnet_grocery_list.Models; using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list { public class Startup {

    //... everything else  
    public void ConfigureServices(IServiceCollection services)  
    {  
      // Configures GroceryListContext to use in-memory database  
      services.AddDbContext(opt => opt.UseInMemoryDatabase());  
      // Add framework services.  
      services.AddMvc();  
    }  
    
    } }

    6 and

    //... other using statements using dotnet_grocery_list.Models; using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list { public class Startup {

    //... everything else  
    public void ConfigureServices(IServiceCollection services)  
    {  
      // Configures GroceryListContext to use in-memory database  
      services.AddDbContext(opt => opt.UseInMemoryDatabase());  
      // Add framework services.  
      services.AddMvc();  
    }  
    
    } }

    7—which are the methods that generate the

    //... other using statements using dotnet_grocery_list.Models; using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list { public class Startup {

    //... everything else  
    public void ConfigureServices(IServiceCollection services)  
    {  
      // Configures GroceryListContext to use in-memory database  
      services.AddDbContext(opt => opt.UseInMemoryDatabase());  
      // Add framework services.  
      services.AddMvc();  
    }  
    
    } }

    8 and the

    //... other using statements using dotnet_grocery_list.Models; using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list { public class Startup {

    //... everything else  
    public void ConfigureServices(IServiceCollection services)  
    {  
      // Configures GroceryListContext to use in-memory database  
      services.AddDbContext(opt => opt.UseInMemoryDatabase());  
      // Add framework services.  
      services.AddMvc();  
    }  
    
    } }

    9 for users. If you are wondering what is the difference between these two tokens and when to use one or another, take a look here.

The

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

3 class accepted by the

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using dotnet_grocery_list.Models;
using System.Linq;
namespace dotnet_grocery_list.Controllers
{
  [Route("api/[controller]")]
  public class GroceryListController : Controller
  {
    private readonly GroceryListContext _context;
    public GroceryListController(GroceryListContext context)
    {
      _context = context;
      if (_context.GroceryList.Count() == 0)
      {
        _context.GroceryList.Add(new GroceryItem { Description = "Item1" });
        _context.SaveChanges();
      }
    }     
    [HttpGet]
    public IEnumerable GetAll()
    {
      return _context.GroceryList.ToList();
    }
    [HttpGet("{id}", Name = "GetGroceryItem")]
    public IActionResult GetById(long id)
    {
      var item = _context.GroceryList.FirstOrDefault(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      return new ObjectResult(item);
    }
    [HttpPost]
    public IActionResult Create([FromBody] GroceryItem item)
    {
      if (item == null)
      {
        return BadRequest();
      }
      _context.GroceryList.Add(item);
      _context.SaveChanges();
      return CreatedAtRoute("GetGroceryItem", new { id = item.Id }, item);
    }
    [HttpDelete("{id}")]
    public IActionResult Delete(long id)
    {
      var item = _context.GroceryList.First(t => t.Id == id);
      if (item == null)
      {
        return NotFound();
      }
      _context.GroceryList.Remove(item);
      _context.SaveChanges();
      return new NoContentResult();
    }
  }
}

2 method, has not been created yet, so let's do it now. Let's create a new file called

# get all items from the grocery list
curl http://localhost:5000/api/grocerylist
# add `buy some milk` to the list
curl -H "Content-Type: application/json" -X POST -d '{
    "description": "buy some milk"
}'  http://localhost:5000/api/grocerylist
# delete item with id 1
curl -X DELETE http://localhost:5000/api/grocerylist/1

2 in the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

7 directory and add the following code to it:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

5

Whenever users successfully register themselves in our application, their credentials get persisted to the database. To enable this feature, we need to create a class that will act as the persistence layer. This class will be created in a new file called

# get all items from the grocery list
curl http://localhost:5000/api/grocerylist
# add `buy some milk` to the list
curl -H "Content-Type: application/json" -X POST -d '{
    "description": "buy some milk"
}'  http://localhost:5000/api/grocerylist
# delete item with id 1
curl -X DELETE http://localhost:5000/api/grocerylist/1

4, in the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

7 directory, with the following code:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

6

And then we need to make two changes in the add the

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

9 class. First we need to add two lines, with the

# get all items from the grocery list
curl http://localhost:5000/api/grocerylist
# add `buy some milk` to the list
curl -H "Content-Type: application/json" -X POST -d '{
    "description": "buy some milk"
}'  http://localhost:5000/api/grocerylist
# delete item with id 1
curl -X DELETE http://localhost:5000/api/grocerylist/1

7 statements, as the first two lines of the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

6. And then we need to configure our app to use the identity framework:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

7

After updating our application as explained above, we are now able to handle user registration. To check if everything is working as expected, let's start our application (which can be done through our IDE or through the

# get all items from the grocery list
curl http://localhost:5000/api/grocerylist
# add `buy some milk` to the list
curl -H "Content-Type: application/json" -X POST -d '{
    "description": "buy some milk"
}'  http://localhost:5000/api/grocerylist
# delete item with id 1
curl -X DELETE http://localhost:5000/api/grocerylist/1

9 command), and issue the following HTTP POST request:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

8

Note that the password must contain at least one number, one lowercase character, one uppercase character, one non alphanumeric character, and at least six characters. Just like the example above.

If we managed to update our application accordingly, the answer to our request will be a JSON object with two properties:

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

8 and

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

9.

Enabling Users to Sign In

Now that our users are already able to create accounts on our ASP.NET Core application, we need to add a feature for the existing users to sign in. The sign in process is quite similar to the registration process, the difference is that when a user signs in, it won't be registered in the database. Its credentials are going to be used to query the database to see if a user with the email and password combination exists, and if it does, the

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

8 and

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

9 will be generated and sent back.

To enable this feature, we are going to add the following method to the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

04 class recently created:

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

9

To sign in into the application, we just need to issue an HTTP POST similar to the registration one. The difference is the endpoint URL, that now contains a

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

05 suffix:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

0

Protecting ASP.NET Core API

Even though we have created the two endpoints to enable users to register and to sign in, our grocery list application API is still publicly available. To secure all endpoints exposed by the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

4 class, we just need to add the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

07 attribute to this class, like shown below:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

1

If we start the application now, and issue an HTTP GET request to any endpoint of the

|- Controllers
  |- HomeController.cs
|- Views
  |- ...
|- wwwroot
  |- ...
|- appsettings.json
|- Program.cs
|- Startup.cs

4 class, we will get a 404 (Not Found) response from the server. You would probably expect a 401 (Unauthorized) answer, but 404 was sent back because when a user is not logged in they are redirect to a login web page. This web page is not provided by default by ASP.NET Core, and therefore the request ends up being answered with a 404 response.

To circumvent this behavior, we can configure the Identity framework in the

dotnet add package Microsoft.EntityFrameworkCore.InMemory

6 method of the

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

9 class as follows:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

2

Although we have secured our precious endpoints, we are not ready yet. Even if we send the

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

8 in a HTTP request, we are still going to get a 401 answer, because we have not configured our application to validate JWTs. Let's tackle this issue now.

Validating JWTs with ASP.NET Core

To make our ASP.NET Core application validate the tokens issued by our register and sign in features, we are going to add just a few lines of code to the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

12 method of the

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

9 class. Below is the full source code of this method after adding the lines that validate JWTs:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

3

As you can see, we first have loaded the same configuration properties used to generate tokens, and then added a

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

14 call passing a

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

15 with these properties. With that we are now able to validate JWTs and enable (or block) users carrying these tokens to access the grocery list API. To test the endpoint with

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

16, you can issue the following commands:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

4

Note that we have used

dotnet --info

command output (example)

#.NET Command Line Tools (1.0.4)

Product Information:

Version: 1.0.4

Commit SHA-1 hash: af1e6684fd

Runtime Environment:

OS Name: Mac OS X

OS Version: 10.12

OS Platform: Darwin

RID: osx.10.12-x64

Base Path: /usr/local/share/dotnet/sdk/1.0.4

17 to extract the

//... other using statements using dotnet_grocery_list.Models; using Microsoft.EntityFrameworkCore; namespace dotnet_grocery_list { public class Startup { //... everything else public void ConfigureServices(IServiceCollection services) { // Configures GroceryListContext to use in-memory database services.AddDbContext(opt => opt.UseInMemoryDatabase()); // Add framework services. services.AddMvc(); } } }

8 generated, and then saved it in the

dotnet --info

command output (example)

#.NET Command Line Tools (1.0.4)

Product Information:

Version: 1.0.4

Commit SHA-1 hash: af1e6684fd

Runtime Environment:

OS Name: Mac OS X

OS Version: 10.12

OS Platform: Darwin

RID: osx.10.12-x64

Base Path: /usr/local/share/dotnet/sdk/1.0.4

19 environment variable.

dotnet --info

command output (example)

#.NET Command Line Tools (1.0.4)

Product Information:

Version: 1.0.4

Commit SHA-1 hash: af1e6684fd

Runtime Environment:

OS Name: Mac OS X

OS Version: 10.12

OS Platform: Darwin

RID: osx.10.12-x64

Base Path: /usr/local/share/dotnet/sdk/1.0.4

17 is a lightweight and flexible command-line JSON processor, and its web page with instructions on how to install and use can be found here.

If you need a reference for a ASP.NET Core application with authentication fully implemented, you can take a look at the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

21 branch of this GitHub repository.

"Creating secure RESTful APIs with ASP.NET Core is a piece of cake."

Tweet This

Aside: Securing ASP.NET Core with Auth0

In the following sections, we will see how to use authorization features of OAuth 2.0 to limit access to our ASP.NET Core applications. To learn more about OAuth 2.0, we can refer to the API authorization documentation.

The very first thing we need is to create our own Auth0 account. Luckily, Auth0 has a free tier that supports 7,000 free active users & unlimited logins!

Create a Resource Server (API)

After creating the account, we need to register our application in . On this section, let's click "Create API". Then we have to provide a name ("Contacts API") and an identifier (

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

  1. to our API. We will use the identifier as an audience when configuring clients that will fetch

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

23. For the signing algorithm, let's select RS256.

Installing Dependencies

To use tokens with ASP.NET Core applications, we need to use the JWT middleware. This middleware is provided by the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

24 package. To install this package, let's use the

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

9 command:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

5

Configuration

As requested when creating it, our API will use RS256 as the algorithm for signing tokens. Since RS256 uses a private/public key pair, it verifies the tokens against the public key for our Auth0 account. The ASP.NET Core JWT middleware will handle downloading the JSON Web Key Set (JWKS) file containing the public key for us, and will use that to verify the

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

8 signature.

To add the JWT middleware to our application's middleware pipeline, let's go to the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

12 method of our

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

9 class and add a call to

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

14. This call will pass in an instance of

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

15 configured with our Auth0 properties. The

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

15 needs to specify our Auth0 API Identifier as the

using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list.Models
{
  public class GroceryListContext : DbContext
  {
    public GroceryListContext(DbContextOptions options)
        : base(options)
    {
    }
    public DbSet GroceryList { get; set; }
  }
}

1, and the full path to our Auth0 domain as the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

33:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

6

The JWT middleware integrates with the standard ASP.NET Core Authentication and Authorization mechanisms. Therefore, to secure an endpoint we only need to decorate our controller action with the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

34 attribute:

mkdir dotnet-grocery-list
cd dotnet-grocery-list
dotnet new mvc

7

Creating an Auth0 Client

As the focus of this section is to secure ASP.NET Core with Auth0, we are going to use a live Angular app that has a configurable Auth0 client. Before using this app, we need to create an Auth0 Client that represents it. Let's head to the and click on the "Create Client" button to create this client.

On the popup shown, let's set the name of this new client as "Contacts Client" and choose "Single Page Web App" as the client type. After hitting the "Create" button, we have to go to the "Settings" tab of this client and add

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

35 to the "Allowed Callback URLs" field.

Now we can save the client and head to the sample Angular app secured with Auth0. To use this app, we need to set the correct values for four properties:

  • `

    dotnet --info

    command output (example)

    #.NET Command Line Tools (1.0.4)

Product Information:

Version: 1.0.4

Commit SHA-1 hash: af1e6684fd

Runtime Environment:

OS Name: Mac OS X

OS Version: 10.12

OS Platform: Darwin

RID: osx.10.12-x64

Base Path: /usr/local/share/dotnet/sdk/1.0.4
36: We have to copy this value from the "Client ID" field of the "Settings" tab of "Contacts Client".
  • ` dotnet --info # command output (example) #.NET Command Line Tools (1.0.4)
# Product Information: # Version: 1.0.4 # Commit SHA-1 hash: af1e6684fd # Runtime Environment: # OS Name: Mac OS X # OS Version: 10.12 # OS Platform: Darwin # RID: osx.10.12-x64 # Base Path: /usr/local/share/dotnet/sdk/1.0.4

37: We can also copy this value from the "Settings" tab of "Contacts Client".

  • `

    dotnet --info

    command output (example)

    #.NET Command Line Tools (1.0.4)

Product Information:

Version: 1.0.4

Commit SHA-1 hash: af1e6684fd

Runtime Environment:

OS Name: Mac OS X

OS Version: 10.12

OS Platform: Darwin

RID: osx.10.12-x64

Base Path: /usr/local/share/dotnet/sdk/1.0.4
38: We have to set this property to meet the identifier of the API that we created earlier (  

dotnet --info

command output (example)

#.NET Command Line Tools (1.0.4)

Product Information:

Version: 1.0.4

Commit SHA-1 hash: af1e6684fd

Runtime Environment:

OS Name: Mac OS X

OS Version: 10.12

OS Platform: Darwin

RID: osx.10.12-x64

Base Path: /usr/local/share/dotnet/sdk/1.0.4
22).
  • ` dotnet --info # command output (example) #.NET Command Line Tools (1.0.4)
# Product Information: # Version: 1.0.4 # Commit SHA-1 hash: af1e6684fd # Runtime Environment: # OS Name: Mac OS X # OS Version: 10.12 # OS Platform: Darwin # RID: osx.10.12-x64 # Base Path: /usr/local/share/dotnet/sdk/1.0.4

40: This property will define the

dotnet --info  
# command output (example)  
#.NET Command Line Tools (1.0.4)  

# Product Information:  
# Version:            1.0.4  
# Commit SHA-1 hash:  af1e6684fd  

# Runtime Environment:  
# OS Name:     Mac OS X  
# OS Version:  10.12  
# OS Platform: Darwin  
# RID:         osx.10.12-x64  
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4  
41 that the
//... other using statements  
using dotnet_grocery_list.Models;  
using Microsoft.EntityFrameworkCore;  
namespace dotnet_grocery_list  
{  
  public class Startup  
  {  
    //... everything else  
    public void ConfigureServices(IServiceCollection services)  
    {  
      // Configures GroceryListContext to use in-memory database  
      services.AddDbContext(opt => opt.UseInMemoryDatabase());  
      // Add framework services.  
      services.AddMvc();  
    }  
  }  
}  
8 will get access to in the backend API. For example:
dotnet --info  
# command output (example)  
#.NET Command Line Tools (1.0.4)  

# Product Information:  
# Version:            1.0.4  
# Commit SHA-1 hash:  af1e6684fd  

# Runtime Environment:  
# OS Name:     Mac OS X  
# OS Version:  10.12  
# OS Platform: Darwin  
# RID:         osx.10.12-x64  
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4  
43 or both
dotnet --info  
# command output (example)  
#.NET Command Line Tools (1.0.4)  

# Product Information:  
# Version:            1.0.4  
# Commit SHA-1 hash:  af1e6684fd  

# Runtime Environment:  
# OS Name:     Mac OS X  
# OS Version:  10.12  
# OS Platform: Darwin  
# RID:         osx.10.12-x64  
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4  
44.

Then we can hit the "Sign In with Auth0" button.

After signing in, we can use the application to submit requests to our secured Node.js API. For example, if we issue a GET request to

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

45, the Angular app will include the

//... other using statements
using dotnet_grocery_list.Models;
using Microsoft.EntityFrameworkCore;
namespace dotnet_grocery_list
{
  public class Startup
  {
    //... everything else
    public void ConfigureServices(IServiceCollection services)
    {
      // Configures GroceryListContext to use in-memory database
      services.AddDbContext(opt => opt.UseInMemoryDatabase());
      // Add framework services.
      services.AddMvc();
    }
  }
}

8 in the

dotnet --info
# command output (example)
#.NET Command Line Tools (1.0.4)

# Product Information:
# Version:            1.0.4
# Commit SHA-1 hash:  af1e6684fd

# Runtime Environment:
# OS Name:     Mac OS X
# OS Version:  10.12
# OS Platform: Darwin
# RID:         osx.10.12-x64
# Base Path:   /usr/local/share/dotnet/sdk/1.0.4

47 header and our API will respond with a list of contacts.

Conclusion

Developing RESTful APIs with ASP.NET Core is easy and can lead to loosely coupled architectures with great performance and scalability. Also, the authentication feature was easy to implement and, with Auth0, can be easily enhanced. With these upsides, alongside with the fact that the whole .NET Core technology is open source and cross platform, we can expect an exponential growth on the interest for this framework, which will result on rich set of open source packages and a thriving community. Therefore, it is a good time to to learn ASP.NET Core and to start writing application with this solution.