Building a Web API on ASP.NET Core for our Aurelia SPA
This post will focus on writing a Web API that will serve our Aurelia SPA with data. The Web API is built on ASP.NET Core and resides in it’s own project. We will be implementing the repository pattern and an in memory repository. A Class Library will be used to hold the model and the repository.
The “Aurelia SPA built with TypeScript and Sass on .NET Core” Series
These are the parts of this blog post series about building a SPA in Aurelia.
Part I – How to: Build an Aurelia SPA with TypeScript on ASP.NET Core
Part II – How to: Build a Web API on ASP.NET Core for an Aurelia SPA
Part III – How to: Fetching data from a Web API on ASP.NET Core to an Aurelia SPA
Part IV – How to: Creating Aurelia Custom Elements with TypeScript
Aim of this Post
First of all, the functionality of the SPA we’re building is to store and manage information about droids. The aim of this post, is to implement the storage layer and also the models for our data. When this post is done we will have a ASP.NET Core Web API that our Aurelia SPA can use. We’ll also have the base models in a Class Library, and use them from our Web API.
Defining the Model and Repository
The droids we’re going to operate on will have a sett of properties describing the object and some pertaining the object storage. The properties describing object storage (create date, change date) is not something we want to send to a client, so we will use a DTO (Data Transfer Objects) to decide what we want send to the clients.
Create the Droid Model
- First add a new folder to the Class Library, name it Model.
- Add a class in the new folder, name it Droid.cs
- Add a new folder to the Class Library named DTO.
- Add a new class in the DTO folder, name that Droid.cs as well.
So what information do we want to store about our droids?
First of we need identifying properties, like name and id’s.
Then we’d like a set of physical attributes listed, like height, weight, any eventual armaments etc.
We also want some meta data on the droid object, it’s good to know when it was created and modified.
Edit the Model.Droid.cs class like this:
using System; using System.Collections.Generic; namespace DWx.Repository.Model { public class Droid { private DateTime CreateDate { get; } = DateTime.UtcNow; private DateTime EditDate { get; } = DateTime.UtcNow; private DateTime DeleteDate { get; } = DateTime.UtcNow; private bool IsDeleted { get; } public int Id { get; set; } public Guid ImperialContractId { get; set; } public string Name { get; set; } public long CreditBalance { get; set; } public string ProductSeries { get; set; } public decimal Height { get; set; } public decimal Weight { get; set; } public IEnumerable<string> Armaments { get; set; } public IEnumerable<string> Equipment { get; set; } public Droid() { CreateDate = DateTime.UtcNow; EditDate = DateTime.UtcNow; DeleteDate = DateTime.MinValue; } public Droid(DTO.Droid dtoDroid) { CreateDate = DateTime.UtcNow; EditDate = DateTime.UtcNow; DeleteDate = DateTime.MinValue; Id = dtoDroid.Id; ImperialContractId = dtoDroid.ImperialContractId; Name = dtoDroid.Name; CreditBalance = dtoDroid.CreditBalance; ProductSeries = dtoDroid.ProductSeries; Height = dtoDroid.Height; Weight = dtoDroid.Weight; Armaments = dtoDroid.Armaments; Equipment = dtoDroid.Equipment; } } }
Edit the DTO.Droid.cs like this:
using System; using System.Collections.Generic; namespace DWx.Repository.DTO { public class Droid { public Droid() { } public Droid(Model.Droid modelDroid) { Id = modelDroid.Id; ImperialContractId = modelDroid.ImperialContractId; Name = modelDroid.Name; CreditBalance = modelDroid.CreditBalance; ProductSeries = modelDroid.ProductSeries; Height = modelDroid.Height; Weight = modelDroid.Weight; Armaments = modelDroid.Armaments; Equipment = modelDroid.Equipment; } public int Id { get; set; } public Guid ImperialContractId { get; set; } public string Name { get; set; } public long CreditBalance { get; set; } public string ProductSeries { get; set; } public decimal Height { get; set; } public decimal Weight { get; set; } public IEnumerable<string> Armaments { get; set; } public IEnumerable<string> Equipment { get; set; } } }
Create the Repository
What do we want our repository to do then?
First of all We want to specify an interface for the Repository! This way we can swap it around for other implementations later on, without breaking our contract.
This will also help with unit testing, as having an interface to adhere too makes it easy to substitute in mock classes for testing.
- Create a folder in the root of the Class Library named Repository.
- Create a new interface, name it IDroidRepository.cs
- Create a new class named DroidRepository.cs
Implement the Repository Interface
As for our repository we want to be able to perform the standard CRUD operations (Create, Read, Update and Delete), but also support partial update that will come in handy in our SPA.
Implement the repository interface like this:
using System.Collections.Generic; using DWx.Repository.DTO; namespace DWx.Repository.Repository { public interface IDroidRepository { IEnumerable<Droid> GetAll(); bool Create(Droid newDroid); Droid Get(int id); Droid Update(Droid droid); Droid Delete(int id); } }
Implement the Repository Methods
Time to start with some real implementation.
Open the DroidRepository.cs file, and make it derive from the IDroidRepository interface. To speed up development you can put your cursor and use “the light bulb” and make it implement the interface.
As for data storage we’re just going to use a dictionary to start with. So no real persistence layer, yet. As it’s only one static dictionary keeping the data, we can end up with multiple calls from multiple threads at the same time, so we need to use the ConcurrentDictionary implementation.
Then implement the repository, the following is the one I use:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using DWx.Repository.DTO; namespace DWx.Repository.Repository { public class DroidRepository : IDroidRepository { private static ConcurrentDictionary<int, Model.Droid> repo { get; } = new ConcurrentDictionary<int, Model.Droid>(); private static int id; public DroidRepository() { Seed(); } public bool Create(Droid newDroid) { var droid = new Model.Droid(newDroid); newDroid.Id = id++; repo.TryAdd(newDroid.Id, new Model.Droid(newDroid)); return true; } public Droid Delete(int id) { Model.Droid modelDroid; repo.TryRemove(id, out modelDroid); return new Droid(modelDroid); } public Droid Get(int id) { var droid = repo.Values.FirstOrDefault(d => d.Id == id); if (droid != null) { return new Droid(droid); } return null; } public IEnumerable<Droid> GetAll() { var droids = new List<Droid>(); foreach (var droid in repo.Values) { var newDroid = new Droid(droid); droids.Add(newDroid); } return droids.OrderBy(d => d.Id); } public Droid Update(Droid droid) { if (repo.ContainsKey(droid.Id)) { droid.Id = repo[droid.Id].Id; repo[droid.Id] = new Model.Droid(droid); return droid; } return null; } /// <summary> /// Seed the database with a few initial droids /// </summary> private static void Seed() { var ig88B = new Model.Droid { Id = id++, ImperialContractId = Guid.Parse("0B450FDD-F484-423B-8685-4193E9FA583D"), Name = "IG-88B", CreditBalance = 4500000, ProductSeries = "IG-88", Height = 1.96M, Armaments = new List<string> { "DAS-430 Neural Inhibitor", "Heavy pulse cannon", "Poison darts", "Toxic gas dispensers", "Vibroblades" }, Equipment = new List<string>() }; repo.TryAdd(ig88B.Id, ig88B); var c3po = new Model.Droid { Id = id++, Name = "C-3PO", ProductSeries = "3PO-series Protocol Droid", Height = 1.71M, Armaments = new List<string>(), Equipment = new List<string> { "TranLang III communication module" } }; repo.TryAdd(c3po.Id, c3po); var r2d2 = new Model.Droid { Id = id++, Name = "R2-D2", ProductSeries = "R-Series", Height = 0.96M, Armaments = new List<string> { "Buzz saw", "Electric pike" }, Equipment = new List<string> { "Drinks tray (only on sail barge)", "Fusion welder", "Com link", "Power recharge coupler", "Rocket boosters", "Holographic projector/recorder", "Motorized, all-terrain treads", "Retractable third leg", "Periscope", "Fire extinguisher", "Hidden lightsaber compartment with ejector", "Data probe", "Life-form scanner", "Utility arm" } }; repo.TryAdd(r2d2.Id, r2d2); } } }
Add the Repository to the ASP.NET Core Dependency Injection System
Last thing we need to do is add the repository to the Dependency Injection system in ASP.NET Core, so that it can be inject into, and used from our controllers.
Open Startup.cs and modify ConfigureServices like this:
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); //DI services.AddSingleton<IDroidRepository, DroidRepository>(); }
Implementing the Web API
Finally time to start working on the Web API!
But before we start implementing the actual methods, we need to change a few things in the setup of the project.
Preparing the Web API Project
To make the development experience a little smoother, add the watch tool to the Web API project if you haven’t already done so. (See the first episode of this post series if you don’t know how)
Then we want to change the base address to localhost:5005 instead of localhost:5000(as port 5000 is used by the Aurelia SPA). Modify the WebHostBuilder creation in Program.cs by adding a call to USeUrls(). Program.cs should look something like this:
public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseUrls("http://localhost:5005") .Build(); host.Run(); }
Adding a Reference to the Repository Library
To access the repository functionality we implemented, we need to import a reference to our Class Library DWx.Repository.
Using Visual Studio
Right click the References node in the DWx.API project and select Add Reference….
Not Using an IDE
It’s also possible to just edit the project.json file. (This option is going away soonβ’ though, so enjoy it while it lasts!)
Just open the project.json file and add the following line under the dependencies section:
"DWx.Repository": "1.0.0-*"
Adding a Controller
Controllers are what’s used to serve the clients with information from a Web API.
So now add a class called DroidsController in the Web API under the Controllers folder.
If you got the ValuesController.cs left you can remove it, as it’s just examples created with the project.
The base implementation of the DroidsController should look like this:
using DWx.Repository.Repository; using Microsoft.AspNetCore.Mvc; namespace DWx.API.Controllers { [Route("api/[controller]")] public class DroidsController { readonly IDroidRepository droidRepo; public DroidsController(IDroidRepository repository) { droidRepo = repository; } } }
Implementing the GetAll Method
Add a method to the DroidsController class called GetAll.
Implement it like this:
[HttpGet] public IActionResult GetAll() { var droids = droidRepo.GetAll(); return new OkObjectResult(droids); }
Testing the Web API
Open a CLI and navigate to the root of the Web API Project. Start the server with dotnet watch run.
Now we can test the GetAll method we just implemented with some tool that can fire of web requests, for ex Postman.
In Postman call localhost:5005 with a GET and see what happens. It should return an array of Droids, the ones we defined in the Seed method in the DroidRepository.
Implementing the GetById Method
Add a method to the DroidsController class called GetById.
In this method we’re going to use the Http attribute to name it, because we want to reference this method by name later on. Implement the method like this:
[HttpGet("{id}", Name = nameof(GetById))] public IActionResult GetById(int id) { var droid = droidRepo.Get(id); if (droid == null) { return new NotFoundObjectResult( new Error { HttpCode = 404, Message = $"Droid with id:{id} - No such Droid in database!" } ); } return new OkObjectResult(droidRepo.Get(id)); }
Testing it with postman by sending a GET for id 0 gives:
Implementing the Create Method
Add a method to the DroidsController class called Create.
Implement it like this:
[HttpPost] public IActionResult Create([FromBody] Droid droid) { if (!ModelState.IsValid) { return new BadRequestObjectResult(new Error { HttpCode = 400, Message = $"Invalid payload: {ModelState}" }); } var result = droidRepo.Create(droid); return new CreatedAtRouteResult(nameof(GetById), new { id = droid.Id }, droid); }
Visual Studio will mark ModelState with a squiggly, as it doesn’t know what this is yet. Easiest way to get access to ModelState is to derive our class from Controller, so just do that for the class and this will compile.
public class DroidsController : Controller
When testing this with Postman, we issue a Post to localhost:5005 with the droid object in the body of the call. We also need to set a header for this to work, namely Content-Type that needs to be set to application/json.
Testing the Create call with Postman:
Implementing the Delete Method
Add a method to the DroidsController class, called Delete.
Implement it like this:
[HttpDelete("{id}")] public IActionResult Delete(int id) { var result = droidRepo.Delete(id); if (result == null) { return new BadRequestObjectResult(new Error { HttpCode = 404, Message = "No such Droid in database!" }); } return new NoContentResult(); }
There’s two schools on what to return from a Delete in a Web API, some say return a status code and some say return the deleted object. This method returns a 204 No Content on successful removal of a droid.
Using Postman to test the Delete method:
Implementing the Update Method
Add a method to the DroidsController class, called Update.
Implement it like this:
[HttpPut] public IActionResult Update([FromBody] Droid droid) { if (!ModelState.IsValid) { return new BadRequestObjectResult(new Error { HttpCode = 400, Message = "Invalid payload" }); } var result = droidRepo.Update(droid); if (result == null) { return new NotFoundObjectResult(new Error { HttpCode = 410, Message = "Could not find Droid in database!" }); } return new OkObjectResult(droid); }
Just as with the Create method, the Content-Type header needs to be set to application/json.
Testing the Update by removing a IG-88 it’s armaments:
Next Part – Fetching Data from the API in our Aurelia SPA!
In the next episode we’ll get the Web API going with serving data to our SPA. Will it be straight forward or will we run in to any surprises? π
We’ll also create a first view in Aurelia to show some data. And who knows, maybe we’ll have time to implement our applications first Aurelia element!
Get the Code
The code for this blog series is available on my GitHub repo, you can find it here: DWx-Aurelia-dotNETCore
Until next time,
Happy Coding! π
Some Additional Reading on ASP.NET Core Web API’s
About setting up a minimum viable Web API on ASP.NET Core:
Minimal Web API on ASP.NET Core
On Attribute routing and all it’s intricacies:
How to Master ASP.NET Core Web API Attribute Routing
On using SSL in development for ASP.NET Core:
ASP.NET Core β IIS Express and SSL
Pingback: How to: Fetch data from Web API on ASP.NET Core to an Aurelia SPA - mobilemancer
Pingback: How to: Creating Aurelia Custom Elements with TypeScript - mobilemancer
Pingback: How to: Build an Aurelia SPA with TypeScript on ASP.NET Core - mobilemancer
Pingback: How To: Configure and Use the Router in an Aurelia SPA - mobilemancer
Pingback: How To: Style an Aurelia SPA with Sass - mobilemancer
Pingback: Unit Testing and E2E Testing an Aurelia SPA (CLI) - mobilemancer