ASP.NET Core 2.1: ActionResult

This post is going to take the Contacts API from my ASP.NET Basics set of posts and move it from using IActionResultto ActionResult<T> which was introduced with the 2.1 release. The changes are really simple, but if you are using OpenAPI/Swagger I have a call out later in the post about something I noticed. The code before any changes can be found here.

IActionResult vs ActionResult<T>

The official docs explain the three different ways to return data in an API which are a specific type, IActionResult type, or ActionResult<T> type.

A specific type is great if you don’t have to do any sort of validation or the like, but as soon as you need to return a different HTTP status than OK is no longer sufficient. This is where you would have to move to IActionResult.

IActionResult allows different HTTP statuses to be returned. In the following example, NotFound is returned if a contact with the supplied ID isn’t found or OK(contact) if a contact is found.

public async Task<IActionResult> GetContact([FromRoute] int id)
{
     var contact = await _context.Contact
                                 .SingleOrDefaultAsync(m => m.Id == id);

     if (contact == null)
     {
        return NotFound();
     }
    
     return Ok(contact);
}

The advantage of ActionResult<T> it is the return type of the function is clear. You can see in the following example where GetContact has been changed to use ActionResult<T> that if all goes well you will be dealing with a Contact object in the end without the need to wrap the result in an OK.

public async Task<ActionResult<Contact>> GetContact([FromRoute] int id)
{
     var contact = await _context.Contact
                             .SingleOrDefaultAsync(m => m.Id == id);

     if (contact == null)
     {
        return NotFound();
    }

    return contact;
}

OpenAPI/Swagger

If you are using OpenAPI/Swagger in your project with a function with the following definition it will automatically pick up the return type if you switch to using ActionResult<T>.

public async Task<ActionResult<Contact>> GetContact([FromRoute] int id)

The above function results in the following in OpenAPI/Swagger UI.

This is awesome and saves you from having to ProducesResponseType attributes to your API functions. Just note that as soon as you do add a ProducesResponseType for say a NotFound response you will still need include a response for OK with the proper type or you will lose the return type in the OpenAPI/Swagger UI.

I’m calling that last bit out because I spent time trying to figure out why all the samples I saw the return type was automatically picked up, but in my sample application it wasn’t.

Wrapping Up

I’m a huge fan of ActionResult<T> mostly because of the clarity it adds to API function definitions. The fact that OpenAPI/Swagger can pick up on it in the simple cases is an added bonus.

If you are looking for more info check out the Exploring ActionResult<T> in ASP.NET Core 2.1 post by Joonas Westlin in which there is more info on how the functionality is actually implemented. If you didn’t already make sure and check out the Controller action return types in ASP.NET Core Web API page in the official docs for a detailed comparison of the return type options for APIs.

The completed code can be found here.

Pass ASP.NET Core Appsettings Values to Angular via an API Call

There have been a few issues opened on the repo I have showing usage of Angular, Identity Server 4, and ASP.NET Core together that related to incompatibilities with the newer versions of Angular. In an effort to fix this issue the plan was to recreate the client application using the new Angular template from Microsoft which from what I read should address the issue.

The code before any changes can be found here, but in this case, the whole client application has been recreated so the starting point may not be super helpful.

The Problem

For the most part, this worked well, but the problem can when I needed to use some configuration values from ASP.NET Core in my new Angular application. The previous versions of the template used server-side rendering which I utilized to pass the configuration values. The new template doesn’t use server-side rendering by default and I wanted to find a way to solve the issue without requiring server-side rendering.

The other issue is that I want to be able to run this application in Azure and set the configuration values as environment variables. While Angular seems to have support for environment files finding a solution that used a systems environment variables turned out too not be simple.

Configuration API Endpoint

Since the configuration values I need to get to the client application are secret I decided to go the route of pulling them via an API call back to the same ASP.NET Core application that is hosting the Angular Application, which is the Client App project in the sample solution.

I added a ConfigurationController.cs class to the Controller directory with the following contents.

[Produces("application/json")]
[Route("api/Configuration")]
public class ConfigurationController : Controller
{
    private readonly IConfiguration _configuration;

    public ConfigurationController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpGet("[action]")]
    public IActionResult ConfigurationData()
    {
        return Ok(new Dictionary<string, string>
        {
            { "IdentityServerAddress", _configuration["IdentityServerAddress"] },
            { "ApiAddress", _configuration["ApiAddress"] }
        });
    }
}

This controller gets constructed with a reference to the application’s configuration which is then used to populate a dictionary with the values my Angular application needs. For completeness, the following is the contents of the application’s appsettings.json file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "IdentityServerAddress": "http://localhost:5000",
  "ApiAddress": "http://localhost:5001/api/"
}

Angular Changes

This is the part that I really struggled to get right. I needed the configuration values from the API above to be available as soon as possible. Thankfully I came across this blog post by Juri Strumpflohner which covers using Angular’s APP_INITIALIZER.

The first thing I need was to create a class in Angular to get the configuration values from the API and serve to them the rest of the Angular application. To do this I added a configuration.service.ts into a new ClientApp/src/app/configuration directory. The full class follows.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ConfigurationService {

  private configuration: IServerConfiguration;

  constructor(private http: HttpClient) { }

  loadConfig() {
    return this.http.get<IServerConfiguration>('/api/Configuration/ConfigurationData')
      .toPromise()
      .then(result => {
        this.configuration = <IServerConfiguration>(result);
      }, error => console.error(error));
  }

  get apiAddress() {
    return this.configuration.ApiAddress;
  }

  get identityServerAddress() {
    return this.configuration.IdentityServerAddress;
  }

}

export interface IServerConfiguration {
  ApiAddress: string;
  IdentityServerAddress: string;
}

This class hits the API to get the configuration values in the loadConfig function and maps it to a class level field. It also provides properties to get the individual configuration values.

As I mentioned above, getting the application to get these configuration values in a timely matter was something I really struggled to do. The first step to using Angular’s APP_INITIALIZER to solve this issue is to change the import from @angular/core to include APP_INITIALIZER and to import the ConfigurationService.  All these changes are being made in the app.module.ts file.

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { ConfigurationService } from "./configuration/configuration.service";

Next, we need to define a function that will call the ConfigurationService.loadConfig function.

const appInitializerFn = (appConfig: ConfigurationService) => {
  return () => {
    return appConfig.loadConfig();
  };
};

Finally, in the providers array add an element for the APP_INITIALIZER and the ConfigurationService.

providers: [
  ConfigurationService,
  {
    provide: APP_INITIALIZER,
    useFactory: appInitializerFn,
    multi: true,
    deps: [ConfigurationService]
  }]

 Wrapping Up

This is one of those things that turned out to be way more complicated than I expected. Thankfully with the above changes, I was able to get it working. I hope this saves you all some time. The code with all the changes can be found here.

Basic ASP.NET Core API Test with Postman

I had a reader email me about using Postman with ASP.NET Core API base on this post from a couple of years ago. Rather than working through that their specific issues are with that code, I thought it might be more helpful to write a post on creating a super basic ASP.NET Core API and use Postman to test it.

API Creation

We are going to use the .NET CLI to create and run API project so no Visual Studio or other IDE will be needed. The first step is to open a command prompt and navigate to (or create) the directory where you want the API project to live. Run the following command to create the API project.

dotnet new webapi

The webapi template creates a ValuesController with a Get action that returns an array with two values in it which we will be using as our test endpoint.

After the process finished we can now run the project using the following command.

dotnet run

After the run command, you should see something like the following.

Hosting environment: Production
Content root path: C:\YourProjectPath\ApiTest
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

The key bit in the above you need to look for is the Now listening on line as that is the URL we will need to use in Postman to test.

Testing with Postman

Postman is a great tool that to use when developing an API. It allows me to exercise all the functions of the API before any clients have been built. You can do some of the same things using a browser, but Postman was built for this type of usage and it shows. Postman is free and you can grab it here.

Run Postman and you will see something similar to the following screenshot.

For our simple test we want to do a Get request, which is the default, so all we need to do is past the URL from above into the address box and add in the route to the controller we are trying to test. For our sample to test the Get action on the ValuesController our URL ends up being http://localhost:5000/api/values.

Click the Send button and the results will show the lower area Postman (the large red box in the screenshot).

Wrapping Up

This is the simplest setup I could think of to get up and going with Postman and ASP.NET Core. Postman has so many more functions than I showed in this post so I hope this will be a good jumping off point for you all to learn more about this great tool.

Electron.NET with a Web API

This post will be expanding on the introduction to Electron.NET that I did here to add in a Web API hit to pull some data as well as the UI on the Electron side to use this data. The code before any changes can be found here.

API Creation

To create the API I used the following from the command prompt in the folder where I wanted the new project to be created.

dotnet new webapi

API Data

Now that the API project is created we need to add in the ability to interact with a database with Entity Framework Core. Adding in Entity Framework Core ended up turning into a post of its own when you can read here.

The model and DB Context of the API project match what was in the blog post I linked above, but I am going to include them here. The following is the model.

public class Contact
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Subregion { get; set; }
    public string PostalCode { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
}

Next, is the DB Context, which is empty other than the DB Set for the contacts table.

public class ContactsDbContext : DbContext
{
    public DbSet<Contact> Contacts { get; set; }

    public ContactsDbContext(DbContextOptions<ContactsDbContext> options)
        : base(options)
    {

    }
}

With our model and context setup, we can run the following two commands to add the initial migration and apply the migration to the database.

dotnet ef migrations add Contacts
dotnet ef database update

API Endpoints

The API is just going to handle the basic CRUD (create, read, update, delete) operations for contact. Instead of hand coding the controller we are going to use some code generation provided by Microsoft. First, we need to add the Microsoft.VisualStudio.Web.CodeGeneration.Design NuGet package to the API project using the following command in a command prompt set to the root of the API project.

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

Now with the above package installed, we can use the following command to generate a controller with the CRUD operations already implemented.

dotnet aspnet-codegenerator controller -name ContactsController --model Contact --dataContext ContactsDbContext -outDir Controllers -api

There is a lot of switches when using aspnet-codegenerator. The following is a rundown of the ones used above.

  • controller tells the code generator we are creating a controller
  • name defines the name of the resulting controller
  • model is the model class that will be used for the generation
  • dataContext is the DB Context that will be used for the generation
  • outDir is the directory the output will be in relative to the current directory of your command prompt
  • api tells the code generator this controller is for a REST style API and that no views should be generated

With the code generation complete the API should be good to go.

Electron Model

Over in the Electron project, we need a model to match the data the API is returning. This could be the point where a third project is added to allow the API and the Electron app to share common items, but just to keep the example simple I’m just going add a copy of the contact model from the API project to the Electron project.  The following is the full contact model class.

public class Contact
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Subregion { get; set; }
    public string PostalCode { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
}

Electron Views

Now that we have a model in our Electron project we need to create the views that go along with it. Start by adding the code generation package like we did above using the following command.

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

Unfortunately, controller generation needs a DBContext to work which our project doesn’t have, so we have to take the long way about to generate our views and then manually create a controller to go with them. In order to get view generation to work, I had to add references to the Entity Framework Core Tools package using the following command.

dotnet add package Microsoft.EntityFrameworkCore.Tools

In the csproj file add the following .NET CLI tool reference.

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.2" />

Now the project is ready to use the command prompt to generate the views we will need for our CRUD operations related to our contacts. Use the following commands to create the full range of views needed (Create, Edit, List, Delete, Details).

dotnet aspnet-codegenerator view Create Create --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Edit Edit --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Index List --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Delete Delete --model Contact --useDefaultLayout -outDir Views/Contacts

dotnet aspnet-codegenerator view Details Details --model Contact --useDefaultLayout -outDir Views/Contacts

Again there is a lot of switches when using aspnet-codegenerator. The following is a rundown of the ones used above.

  • view  tells the code generator we are creating a view
  • the next two items are the name of the view and the name of the view template
  • model is the model class that will be used for the generation
  • useDefaultLayout uses the default layout (surprise!)
  • outDir is the directory the output will be in relative to the current directory of your command prompt

The Index.cshtml generated above comes with links for Edit, Details, and Delete that won’t work as generated. Open the file and make the following changes to pass the key of the contact trying to be opened.

Before:
@Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
@Html.ActionLink("Details", "Details", new {/* id=item.PrimaryKey */ }) |
@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })

After:
@Html.ActionLink("Edit", "Edit", new {  id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })

Electron Controller

With the views complete let’s add a ContactsController.cs to the Controllers directory. The code for the controller follows, but I’m not going to go into the details. I took a controller from another contact base project and just replaces all the Entity Framework stuff with calls to the API we created above. Please don’t use this as an example of how something like this should be done it is just quick and dirty to show that it can work.

public class ContactsController : Controller
{
    private string _apiBaseUrl = "http://localhost:5000/api/contacts/";

    // GET: Contacts
    public async Task<IActionResult> Index()
    {
        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            return View(JsonConvert.DeserializeObject<List<Contact>>(await (await client.GetAsync("")).Content.ReadAsStringAsync()));
        }
    }

    // GET: Contacts/Details/5
    public async Task<IActionResult> Details(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            var contact = JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync(id.ToString())).Content.ReadAsStringAsync());

            if (contact == null)
            {
                return NotFound();
            }

            return View(contact);
        }
    }

    // GET: Contacts/Create
    public IActionResult Create()
    {
        return View();
    }

    // POST: Contacts/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,Address,City,Email,Name,Phone,PostalCode,State")] Contact contact)
    {
        if (ModelState.IsValid)
        {
            using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
            {
                await client.PostAsync("", new StringContent(JsonConvert.SerializeObject(contact), Encoding.UTF8, "application/json"));
            }

            return RedirectToAction("Index");
        }
        return View(contact);
    }

    // GET: Contacts/Edit/5
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            var contact = JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync(id.ToString())).Content.ReadAsStringAsync());

            if (contact == null)
            {
                return NotFound();
            }

            return View(contact);
        }
    }

    // POST: Contacts/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("Id,Address,City,Email,Name,Phone,PostalCode,State")] Contact contact)
    {
        if (id != contact.Id)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
            {
                await client.PutAsync(id.ToString(), new StringContent(JsonConvert.SerializeObject(contact), Encoding.UTF8, "application/json"));
            }
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    // GET: Contacts/Delete/5
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            var contact = JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync(id.ToString())).Content.ReadAsStringAsync());

            if (contact == null)
            {
                return NotFound();
            }

            return View(contact);
        }

    }

    // POST: Contacts/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            await client.DeleteAsync(id.ToString());
            return RedirectToAction("Index");
        }
    }

    private async Task<bool> ContactExists(int id)
    {
        using (var client = new HttpClient { BaseAddress = new Uri(_apiBaseUrl) })
        {
            return JsonConvert.DeserializeObject<Contact>(await (await client.GetAsync("id")).Content.ReadAsStringAsync()) != null;
        }
    }
}

Electron Add Link To Navigation

The final step to add a link to the list of contacts to the navigation bar of the application. Open the _Layout.cshtml and in the unordered list for the nav bar add the following line.

<li><a asp-area="" asp-controller="Contacts" asp-action="Index">Contacts</a></li>

Wrapping Up

That is all the changes to get the application up and running. If you run the API and then use dotnet electronize start from a command prompt in the ElectronTest project root all should be good to go.

The completed code can be found here.

Refit Basics

A few weeks ago I was watching this episode of the ASP.NET Community Standup and they had Ryan Nowak on to talk about the new HttpClientFactory coming in the 2.1 release and a question came up about compatibility with Refit. I had been meaning to check out Refit but had honestly forgotten about it. This post is going to be a very basic introduction to Refit.

What is it?

In the author’s (Paul Betts) words, Refit is the automatic type-safe REST library for .NET Core, Xamarin and .NET. Cool, but what does that mean? Basically, Refit allows you to define an interface for an API that your application wants to call and using that is hides way all the HTTP and JSON serialization/deserialization bits.

Sample project creation

To test Refit out I created a very simple .NET Core console application. To do the same open a command prompt in the directory you want the project using the following command.

dotnet new console

For this project, I am using Visual Studio Code as my editor. Since VS Code doesn’t have a NuGet UI build in (maybe there is an extension?) I used the following command to add Refit to the project.

dotnet add package Refit

Or if you prefer you can add the following to your csproj file.

<ItemGroup>
  <PackageReference Include="Refit" Version="4.3.0" />
</ItemGroup>

The API

Instead of creating an API I searched the internet for a free one I could use. I ended up using CountryAPI. The following is a sample of what a response from the API looks like.

{  
   "IsSuccess":true,
   "UserMessage":null,
   "TechnicalMessage":null,
   "TotalCount":1,
   "Response":[  
      {  
         "Name":"Afghanistan",
         "Alpha2Code":"AF",
         "Alpha3Code":"AFG",
         "NativeName":"افغانستان",
         "Region":"Asia",
         "SubRegion":"Southern Asia",
         "Latitude":"33",
         "Longitude":"65",
         "Area":652230,
         "NumericCode":4,
         "NativeLanguage":"pus",
         "CurrencyCode":"AFN",
         "CurrencyName":"Afghan afghani",
         "CurrencySymbol":"؋",
         "Flag":"https://api.backendless.com/2F26DFBF-433C-51CC-FF56-830CEA93BF00/473FB5A9-D20E-8D3E-FF01-E93D9D780A00/files/CountryFlags/afg.svg",
         "FlagPng":"https://api.backendless.com/2F26DFBF-433C-51CC-FF56-830CEA93BF00/473FB5A9-D20E-8D3E-FF01-E93D9D780A00/files/CountryFlagsPng/afg.png"
      }]
}

Classes

Now that we know what the API response looks like classes can be created to match its structure. In this case, I have two classes one for response and one for the actual country data.

public class ApiResponse<T>
{
    public bool IsSuccess {get; set;}
    public string UserMessage {get; set;}
    public string TechnicalMessage {get; set;}
    public int TotalCount {get; set;}
    public List<T> Response {get; set;}
}

public class Country
{
    public string Name { get; set; }
    public string Alpha2Code { get; set; }
    public string Alpha3Code { get; set; }
    public string NativeName { get; set; }
    public string Region { get; set; }
    public string SubRegion { get; set; }
}

API Interface for Refit

With the classes for the response setup, we can now define the interface that will be used by Refit when calling the API. The following interface defines a function to get all countries and another function that gets countries that speak a specific native language.

public interface ICountryApi
{
    [Get("/v1/Country/getCountries")]
    Task<ApiResponse<Country>> GetCountries();
    
    [Get("/v1/Country/getCountries")]
    Task<ApiResponse<Country>> GetCountriesByLanguage([AliasAs("pNativeLanguage")]string language);
}

The attributes on the functions are part of the magic of Refit. In the cases above both of the calls are HTTP Get requests which is why they are using the Get attribute. Refit does support other verbs as well.

The other thing of note here is the AliasAs on the parameter of the second function call. This attribute can be used to control what gets put in the query string and keeps cryptic names from the API from spreading to other places in your code.

Calling the API

The following is the full code from my Program class that shows the usage of Refit with both of the API calls defined above.

public static async Task Main(string[] args)
{
    var api = RestService.For<ICountryApi>(" http://countryapi.gear.host");
    
    var countries = await api.GetCountries();
    OutputCountires(countries.Response);

    Console.WriteLine("Enter a language to filter by:");    
    var language = Console.ReadLine();
    var filteredCountries = await api.GetCountriesByLanguage(language);
    OutputCountires(filteredCountries.Response);

    Console.ReadLine();
}

private static void OutputCountires(List<Country> countries)
{
   countries.ForEach(c => Console.WriteLine($"{c.Name} - {c.Region} - {c.SubRegion}"));
}

The following line is defining a call to a rest API for a specific interface.

var api = RestService.For<ICountryApi>(" http://countryapi.gear.host");

Now that we have a reference to the API it can be called asynchronously to get the data from the API.

var countries = await api.GetCountries();

The rest of the app is more of the same just using the other API call.

Gotchas

In order to use async Task Main a change is needed to the project file to set the LangVersion. I just set it to latest, but I believe the minimum for this feature is 7.1.

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp2.0</TargetFramework>
  <LangVersion>latest</LangVersion>
</PropertyGroup>

If you are using VS Code and are using Console.ReadLine() like I am above then a change will be needed for the launch.json file found in the .vscode directory. Look for the console property and set the value to either integratedTerminal or externalTerminal otherwise, the app will be connected to the debug console which will show the output of the application, but doesn’t allow for input.

Wrapping up

Using Refit to makes using APIs super simple. There is a level of magic that I would like to dig into more. I would also like to see how it handles issues and what sort of hooks are provided to address those issue. Based on the Github page Paul has addressed a wide range of the challenges faced with dealing with an API.

As part of writing this posts, I came across two other posts on Refit that might be helpful, one by Jerrie Pelser and the other from Scott Hanselman.

 

Broadcast from an ASP.NET Web API to a UWP Client using SignalR 2

I recently had to take an existing ASP.NET Web API (not core) that was used by a UWP (Universal Windows Platform) client to pull information and modify it to receive notifications from the Web API under some situations. This post is going to cover the creation of a Web API and UWP projects and the modification of the projects to allow notification from the Web API to the UWP client.

Web API Project Creation

In Visual Studio we need to create a new project for the Web API using File > New > Project.

We will be creating an ASP.NET Web Application (.NET Framework) and I’m just naming the project Web API.

On the next screen select that we want a Web API application.

UPW Project Creation

As above in Visual Studio we need to create a new solution and project for the UWP Client using File > New > Project from a new instance of Visual Studio. Select the Blank App (Universal Windows) template.

Next, you will be prompted for the versions of Window 10 to support with the UWP application. I just took the defaults.

You may also want to set your PC into Developer mode.

Add SignalR to the Web API

Now that the initial project creation is done let’s add SignalR to the WebApi project. Inside of Visual Studio right-click on the project and select Manage NuGet Packages.  Select the Browse tab and in the search box enter SignalR and we are looking for the Microsoft.AspNet.SignalR package. Select it and click the Install button.

Configuration

Now that SignalR is installed it needs to be configured on startup of the application. To do this add a Startup class if it doesn’t exist with the following contents.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var hubConfiguration = new HubConfiguration
        {
            EnableDetailedErrors = true,
            EnableJavaScriptProxies = false
        };
        app.MapSignalR(hubConfiguration);

    }
}

The hubConfiguration bit is only needed if you want to change the default settings. In this example, we don’t have any JavaScript clients so we don’t need those proxies and we are learning so detailed errors will come in handy.

Hub

The first thing we need to do is create a new hub which is what SignalR uses to do all of its magic. Hubs are what enable the server (and/or client depending on how you are using it) to make remote calls. Since we are just doing a broadcast the hub class is empty except for a reference to the class that will be handling when to send the broadcast. This property is very important since it will be the trigger for Broadcaster to get instantiated when a client connects.  The following is the full class.

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace WebApi.Hubs
{
    [HubName("BroadcastHub")]
    public class BroadcastHub : Hub
    {
        private readonly Broadcaster _broadcaster = Broadcaster.Instance;
    }
}

Make note of the hub name used as we will need it on the client side.

Next, we have the class that actually does the broadcasting. The following is the full class.

using System;
using System.Threading;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace WebApi.Hubs
{
    public class Broadcaster
    {
        private readonly TimeSpan _updateInterval = 
            TimeSpan.FromMilliseconds(250);
        private Timer _timer;

        private static readonly Lazy<Broadcaster> _instance = 
            new Lazy<Broadcaster>(() => new Broadcaster(GlobalHost.ConnectionManager.GetHubContext<BroadcastHub>().Clients));
        public static Broadcaster Instance => _instance.Value;

        public IHubConnectionContext<dynamic> Clients { get; set; }

        public Broadcaster(IHubConnectionContext<dynamic> clients)
        {
            Clients = clients;
            _timer = new Timer(Broadcast, null, _updateInterval, _updateInterval);
        }

        public void Broadcast(object state)
        {
            Clients.All.Broadcast(DateTime.Now);
        }
    }
}

The above is a singleton which gets created with the list of clients for a specific hub which can be seen in the following line. It has been reformatted from above to try and make it a bit more readable.

private static readonly Lazy<Broadcaster> _instance = 
  new Lazy<Broadcaster>(() => 
             new Broadcaster(GlobalHost
                            .ConnectionManager
                            .GetHubContext<BroadcastHub>()
                            .Clients));

The constructor of the class sets up a timer that calls a Broadcast function which sends the current date and time to all the connected clients. This could be any sort of data, but for this sample, I’m keeping it simple.

Add SignalR to the UWP Client

Now over in the UWP project, we need to add the SignalR client by right-clicking on the project and selecting Manage NuGet Packages. As in the Web API instructions above search for SignalR using the Browse tab. Select the Microsoft.AspNet.SignalR.Client package and click Install.

UI

To keep the sample as simple as possible we are just going to add a TextBlock to the MainPage.xaml to display the results of the broadcast from the Web API. The following is the full code for the page.

<Page
    x:Class="UwpClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UwpClient"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Name="BroadcastResults" />
    </Grid>
</Page>

Now in the code behind we need to connect to the hub on the Web API and define what happens when a broadcast is received. The following is all of the code in the code behind which will be followed up with some comments.

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        InitilizeHub();
    }

    private async void InitilizeHub()
    {
        var hubConnection = new HubConnection("http://localhost:50869");
        var hubProxy = hubConnection.CreateHubProxy("BroadcastHub");

        hubProxy
        .On<DateTime>("Broadcast",
                      async data => 
                            await Dispatcher
                                  .RunAsync(CoreDispatcherPriority.Normal,
                                            () => BroadcastResults.Text = 
                                                          data.ToString()));
        await hubConnection.Start();
    }
}

The hubConnection as you might expect controls the connection with the server and the hubProxy deals with the interaction with a specific hub. In our case, we are saying with the Broadcast function is called on the server we want the client to update the BroadcastResults.Text with the value from the server.

Wrapping Up

Run the Web API and then UWP application and you will see the date time from the server being shown on the client. This is, of course, is the simplest of use cases for SignalR, but it is a great jumping off point to go deeper.

This tutorial from the official docs helped a lot when getting my sample up and running.

The code for this sample can be found here.

Auth0: Introduction and Initial Project Setup

As I started my exploration of Identity Server I listed a few alternatives that provided the same type of functionality, but as a Software as a Service. This series of posts will be covering one of the options I mentioned, Auth0.

The big selling points for Auth0, and other services like it, are that it removes you from having to worry about Auth/User Management and get to the part of your applications that bring value to your customers. As with Identity Server, Auth0 can use OpenID Connect (as well as a lot of other protocols), single sign-on and API Access Control.

Sign-up

The first step in getting started is to sign up for a new account. Here is a link to the sign-up page (not an affiliate link). You can use a username and password or a social login. I’m going the social route using GitHub.

After user creation then there are a couple of setup steps. The first is to choose a tenant domain and region.

Click next and on the second step, there are questions about what the account is going to be used for. In my case, it is a personal, developer, who is just playing around.

Click Create Account to finish the creation of your account which will then land you on the account dashboard page.

Auth0 Setup

Client

From the Auth0 Dashboard click the New Client button. Give the client a name, TestMvc in my case, and select Regular Web Applications as the type.

In a follow-up post I will be covering the Single Page Web Application, but for this post, we are going to be using MVC since it tends to be simpler. The next page defaults to a framework selection which seems to be a guide to getting going for the framework you select. We are going to skip that and click on the Settings tab.

On the settings page, we need to fill in a value for Allowed Callback URLs. The sample client should use http://localhost:50774/signin-auth0. Click the Save Changes button.

API

While we are doing some setup on the Auth0 site we are going to go ahead and set up our API as well. Click the APIs menu option on the left menu.

Then click the Create API button. In the dialog enter a Name and Identifier and click the Create button.

Sample Solution Structure and Setup

The sample solution for this post has two projects.

  • ApiApp – Backend application and is a resource that is will require authorization to access. The API is an ASP.NET Core Web API.
  • ClientApp – Frontend application that will be requesting authorization. This is an ASP.NET Core application that is hosting an Angular (4) application. Note for this post we will be using MVC and not Angular. A future post will deal with the Angular side.

The sample solution with the two projects already added can be found here. If you are using the sample solution feel free to skip the next two sub-sections as they are going over how the projects were created.

To start, add a directory to contain the solution.

API Application

Inside the solution directory, create an ApiApp directory. From the command line in the ApiApp directory run the following command to create a new ASP.NET Core application using the Web API template.

dotnet new webapi
Client Application

Inside the solution directory, create a ClientApp directory. From the command line in the ClientApp directory run the following command to create a new ASP.NET Core application using the Angular template which as of this writing outputs an Angular 4 application.

dotnet new angular

After generation is done run the following to make the all the NPM packages that are required get installed.

npm install
Solution

Inside the solution directory, let’s create a solution file for use with Visual Studio. Run the following command to create a solution file named AspNetCoreAngularAuth0.sln.

dotnet new sln --name AspNetCoreAngularAuth0

Next, run the following two commands to add the API and Client projects to the solution.

dotnet sln add ApiApp/ApiApp.csproj
dotnet sln add ClientApp/ClientApp.csproj

Securing the API Application

Open the appsettings.json file and add a section for Auth0. We are going to need to store the Auth0 domain (tenant domain from sign up) and API Identifier (from the creation of the API at Auth0).  The following is the full file from the API project with the new Auth0 section.

{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "Auth0": {
    "Domain": "yourTenantDomain.auth0.com",
    "ApiIdentifier": "http://localhost:50467/"
  }
}

Next, in the ConfigureServices function of the Startup class add the following to add authentication using the JWT Bearer scheme to the DI system.

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(options =>
{
    options.Authority = $"https://{Configuration["Auth0:Domain"]}/";
    options.Audience = Configuration["Auth0:ApiIdentifier"];
});

In the Configure function add the following line before app.UseMvc() to add authentication to the HTTP pipeline for the API application.

app.UseAuthentication();

The last step in the API for this post is to add a controller that will require authentication. The following is the full code for the AuthTestController that was added to the Controllers directory.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ApiApp.Controllers
{
    [Route("api/[controller]")]
    [Authorize]
    public class AuthTestController : Controller
    {
        [HttpGet]
        public string Get()
        {
            return "Congratulations you are authenticated";
        }
    }
}

Client Application

In the Client Application open the appsettings.json and add the following setting related to Auth0. This is the full file so the logging section was existing.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "Auth0": {
    "Domain": "yourTenantDomain.auth0.com",
    "ClientId": "yourClientId",
    "ClientSecret": "yourClientSecret",
    "CallbackUrl": "http://localhost:50774/signin-auth0",
    "ApiIdentifier": "yourApiIdentifier"
  }
}

If you are going to be checking in your code into a publically accessible source control I recommend you use user secrets instead of appsettings.json. You can read more about user secrets here.

Next, in the ConfigureServices function of the Startup class add the following. I’m not going to go over this code line by line the gist is it is setting up the application to using cookies and Open ID Connect for authentication.

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
    options.Authority = $"https://{Configuration["Auth0:Domain"]}";
    options.ClientId = Configuration["Auth0:ClientId"];
    options.ClientSecret = Configuration["Auth0:ClientSecret"];

    options.ResponseType = "code";
    options.Scope.Clear();
    options.Scope.Add("openid");

    options.CallbackPath = new PathString("/signin-auth0");

    options.ClaimsIssuer = "Auth0";

    options.Events = new OpenIdConnectEvents
    {
        OnRedirectToIdentityProviderForSignOut = (context) =>
        {
            var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";

            var postLogoutUri = context.Properties.RedirectUri;
            if (!string.IsNullOrEmpty(postLogoutUri))
            {
                if (postLogoutUri.StartsWith("/"))
                {
                        // transform to absolute
                        var request = context.Request;
                    postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
                }
                logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
            }

            context.Response.Redirect(logoutUri);
            context.HandleResponse();

            return Task.CompletedTask;
        },
        OnRedirectToIdentityProvider = context =>
        {
            context.ProtocolMessage.SetParameter("audience", Configuration["Auth0:ApiIdentifier"]);
            return Task.FromResult(0);
        }
    };
});

Note that the OnRedirectToIdentityProvider bit is related to getting access to the API.

In the Configure function add the following line before app.UseMvc to add authentication to the HTTP pipeline.

app.UseAuthentication();
Testing Setup

In order to provide a way to test login, logout, and API access without using the Angular portion of the client app, remember that will be a future post, I add an AuthTestController to the Controllers directory with the following.

public class AuthTestController : Controller
{
    public async Task<IActionResult> Index()
    {
        ViewBag.ApiResults = "Not Called";

        var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = 
		     new AuthenticationHeaderValue("Bearer", await HttpContext.GetTokenAsync("access_token"));
        ViewBag.ApiResults = await (await client.GetAsync("http://localhost:50467/api/authtest"))		                     .Content.ReadAsStringAsync();

        return View();
    }

    public async Task Login(string returnUrl = "/")
    {
        await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });
    }

    [Authorize]
    public async Task Logout()
    {
        await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties
        {
            RedirectUri = Url.Action("Index", "Home")
        });
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

Sorry for the formatting, but that sample has long of verbose statements. Nothing crazy going on in this file. There is an Index action that attempts to call the API and then returns a view. The Login and Logout functions do what they say and were pull right from the official docs.

The associated Index.cshtml file was added to the Views/AuthTest directory with the following.

@{
    ViewBag.Title = "Auth Test";
}

@if (User.Identity.IsAuthenticated)
{
    <a asp-controller="AuthTest" asp-action="Logout">Logout</a>
}
else
{
    <a asp-controller="AuthTest" asp-action="Login">Login</a>
}

<p>
    @ViewBag.ApiResults
</p>

This view just shows a link to login or logout and shows the results of the API call. It is ugly but is enough to prove the setup is working.

Wrapping Up

Getting up and running was much fast with Auth0 and would be true of any SASS option I’m sure. It also helped that I have more of an idea of what is going on after all the posts I did on Identity Server. Another positive is Auth0 has some great docs. I used the ASP.NET Core and Web API ones a lot to get this sample application up and running.

Next steps are to get this setup running in the Angular client which should be my next post. The finished code for this post can be found here.

 

Vue: Contact Detail

Last week’s post covered adding a Vue project to the ASP.NET Core Basics example solution. This post is going to cover adding a read-only view of a contact’s detail to being the Vue sample a step closer to being in line with the Angular, Aurelia and React samples. The code before any changes can be found here.

Contact Class

The Contact interface in the contactlist.ts file should be deleted. Add a contact.ts file to the ClientApp/components/contacts/ directory. This will hold the replacement for the interface we just deleted. The full class definition follows.

export class Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;

    constructor(data?: any) {
        if (data == null) return;
        (<any>Object).assign(this, data);
    }

    getAddress(): string {
        return `${this.address} ${this.city}, ${this.state} ${this.postalCode}`;
    }

}

This class is very simple and will be used later to show how a function call can be used as part of a view.

Contact Service

Since the application will now have two places that need to access the ASP.NET Core API lets to refactor the API access into a service. This is the same style used by the Aurelia, Angular and React applications. Add a contactService.ts file to the contacts directory. The service will provide functions to get all contacts or a single contact an ID. The following is the full class.

import { Contact } from './contact';

export class ContactService {
    private baseUrl = 'http://localhost:13322/api/contactsApi/';

    getAll(): Promise<Contact[]> {
        return fetch(this.baseUrl)
            .then(response => response.json() as Promise<Contact[]>)
            .then(contacts => Array.from(contacts, c => new Contact(c)));
    }

    getById(id: string): Promise<Contact> {
        return fetch(`${this.baseUrl}${id}`)
            .then(response => response.json())
            .then(contact => new Contact(contact));
    }
}

Now the API access in the ContactListComponent can be refactored to use the service. First, add imports for contact and service.

import { Contact } from './contact';
import { ContactService } from './contactService';

Next, replace the fetch call with a service call.

Before:
fetch('http://localhost:13322/api/contactsApi/')
    .then(response => response.json() as Promise<Contact[]>)

After:
let contactService = new ContactService();
contactService.getAll()

Contact Detail Component

Add a contactDetail.ts which will be the backing component for the contact detail view. The following is the full class. Pretty much all it is doing is using get contact service to get contact detail by ID when it is mounted.

import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { Contact } from './contact';
import { ContactService } from './contactService';

@Component
export default class ContactDetailComponent extends Vue {
    contact: Contact = new Contact();
    @Prop() id: string;

    mounted() {
        let contactService = new ContactService();
        contactService.getById(this.id)
            .then(data => {
                this.contact = data;
            });
    }
}

Now add a contactDetail.vue.html file for the UI bits of the contact detail. This is basically just HTML with the same binding syntax as before. Notice that the binding of address is a function call. The other Vue things to note are that the details will only show if contact is truthy using the v-if.

<template>
    <div>
        <h1>Contact Details</h1>
        <hr />
        <div v-if="contact">
            <dl class="dl-horizontal">
                <dt>ID</dt>
                <dd>{{ contact.id }}</dd>
                <dt>Name</dt>
                <dd>{{ contact.name }}</dd>
                <dt>Address</dt>
                <dd>{{ contact.getAddress() }}</dd>
                <dt>Phone</dt>
                <dd>{{ contact.phone }}</dd>
                <dt>Email</dt>
                <dd>{{ contact.email }}</dd>
            </dl>
        </div>
        <router-link to="/contactlist">Back to List</router-link>
        <hr />
    </div>
</template>

<script src="./contactDetail.ts"></script>

Routing with a Parameter

Finding the proper way to route with at parameter in Vue was not easy. The way it is done has evolved over time making find the right way a little challenging. The first step is to add a route for the contact detail by adding a new item to the routes array in the boot.ts file.

{ path: '/contactdetail/:id', component: require('./components/contacts/contactDetail.vue.html'), props: true }

The :id in the path denotes a parameter. The props: true bit is also key to getting the ID when in the contact detail component. The contactlist.vue.html needs to be updated to link to the contact detail route.

Before:
<td>{{ contact.id }}</td>

After:
<td>
  <router-link :to="'/contactdetail/' +  contact.id">{{ contact.id }}
  </router-link>
</td>

Finally, in the ContactDetailComponent we need to use the ID. To do so we need an import to allow the use of the Prop decorator.

import { Component, Prop } from 'vue-property-decorator';

Now all that is needed is to apply the decorator to a property of the class that matches the name of the parameter in the route.

@Prop() id: string;

Now the ID can be used to get the proper contact from the contact service. The following is code that was already shown, but the full context of how the prop decorator is used is important so I am repeating it here.

@Component
export default class ContactDetailComponent extends Vue {
    contact: Contact = new Contact();
    @Prop() id: string;

    mounted() {
        let contactService = new ContactService();
        contactService.getById(this.id)
            .then(data => {
                this.contact = data;
            });
    }
}

Wrapping Up

This brings the projects one step closer to being on the same level feature-wise. Look for one more post to get the Vue project features lined up with the other samples in the solution.

Keep in mind that this is my first look at Vue and my examples may or may not be idiomatic.

The code in a finished state can be found here.

ASP.NET Core Basics: Vue with an API

In the past, I have done some exploration on AureliaAngular and React via the ASP.NET Core Basics series. This post is going to take a similar approach using Vue. The code for the project will be in the same repo as the previous basics examples and will be utilizing the same API to pull data. The code before adding the Vue project can be found here.

Project Creation

There is not a template for Vue built into Visual Studio, but there is a set of templates that can be used via the .NET CLI with the dotnet new that includes Vue (as well as Aurelia and Knockout). A full list of available templates can be found here. The template we need is Microsoft.AspNetCore.SpaTemplates and can be installed using the following command from a command prompt.

dotnet new -i "Microsoft.AspNetCore.SpaTemplates::*"

Create a Vue folder at the same level as the other projects, for the samples this would be in the src folder. In the command prompt navigate to the src/Vue/ directory. Run the following command to create the Vue project.

dotnet new vue

After the project generation completes run the following to get the needed npm packages installed.

npm  install

The last step is to get the new project added to the existing solution. Navigate the command prompt to your solution file. For the sample project, this is the root of the repo. Then run the following command (this could also be done through Visual Studio instead of using the CLI).

dotnet sln add src/Vue/Vue.csproj

Adding the Contact List

In the ClientApp/components directory add a new contacts directory to house all the awesome contact related functionality we will be adding. Next, add a new contactlist.ts. To this new file add the interface which defines what a Contact looks like.

interface Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;
}

Above the contact interface, add the following imports needed to create a Vue component.

import Vue from 'vue';
import { Component } from 'vue-property-decorator';

Finally, add the contact list component. This component maintains a list of contacts which gets filled from our existing API when the component is mounted (see the docs for more information on Vue’s lifecycle hooks).

@Component
export default class ContactListComponent extends Vue {
    contacts: Contact[] = [];

    mounted() {
        fetch('http://localhost:13322/api/contactsApi/')
            .then(response => response.json() as Promise<Contact[]>)
            .then(data => {
                this.contacts = data;
            });
    }
}

The following if the full component just to provide full context.

import Vue from 'vue';
import { Component } from 'vue-property-decorator';

@Component
export default class ContactListComponent extends Vue {
    contacts: Contact[] = [];

    mounted() {
        fetch('http://localhost:13322/api/contactsApi/')
            .then(response => response.json() as Promise<Contact[]>)
            .then(data => {
                this.contacts = data;
            });
    }
}

interface Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;
}

Next up is the UI for this contact list component. In the same directory add contactlist.vue.html.  The following is the full file.

<template>
    <div>
        <h1>Contact List</h1>
        <table v-if="contacts.length" class="table">
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="contact in contacts">
                <td>{{ contact.id }}</td>
                <td>{{ contact.name }}</td>
            </tr>
            </tbody>
        </table>

        <p v-else><em>Loading...</em></p>
    </div>
</template>

<script src="./contactlist.ts"></script>

All of the above is pretty straightforward. All the Vue specific items have a v- prefix.

Add the Contact List to Navigation

The last bit needed to have a working contact list is adding it to navigation so the user can get to the list. The routes are defined in the boot.ts file in the routes array. Add the following line to the array to handle our contact list.

{ path: '/contactlist', component: require('./components/contacts/contactlist.vue.html') }

Now that the router knows to handle the contact list it needs to be added to the navigation UI. Open the /navmenu/navmenu.vue.html file and find the unordered list that is the navigation menu. Add a new list item to provide a link to the contact list.

<li>
    <router-link to="/contactlist">
        <span class="glyphicon glyphicon-list-alt"></span> Contact List
    </router-link>
</li>

Wrapping Up

Vue reminds me a lot of Aurelia in its simplicity so far which is awesome. Look for the same progression of posts for Vue that happened with React over the last few weeks.

The code in its final state can be found here.

React: Form for Contact Add

Last week’s post covered adding a read-only view of a contact’s details. As before the goal is to get the React project’s features in line with the Aurelia and Angular samples. This week we will be adding a form to all addition of a new contact. The code before any changes can be found here.

Contact List

On the contact list page, we need to add a link to create a contact. This will be added just after the header in the ContactList.tsx file.

<h1>Contact List</h1>
<Link to={'contactdetail'}>Create New Contact</Link>

Routing

In order to stay in line with the other sample, the ContactDetail component is going to be handling both the read-only view and the view to add a contact. This means the ID that is currently part of the contact detail needs to be optional. The following is the change to make ID optional by adding a question mark.

Before:
<Route path='/contactdetail/:id' component={ContactDetail} />

After:
<Route path='/contactdetail/:id?' component={ContactDetail} />

Contact Service

In the ContactService class, we need to add a save function. This new function will make a post request to the contacts API and return the new contact with the ID from the API to the caller.

save(contact: Contact): Promise<Contact> {
    return fetch(this.baseUrl,
            {
                method: 'post',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json' 
                },
                body: JSON.stringify(contact)
            })
        .then(response => response.json())
        .then(contact => new Contact(contact));
}

Contact Detail

The ContactDetail class is where most of the changes are. I had some trouble getting React’s forms to work directly with the instance of the contact class and ended up just storing the parts of a contact directly in state instead of as an object. I expect this is a failure on my part and not an issue with React. I may revisit this in the future. Below is the new structure of the state used by contact details.

interface ContactDetailState {
    id: string;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;
    loading: boolean;
    redirect: boolean;
}

Next, the constructor needed to be changed to handle the new state structure and to handle being called without an ID.

constructor(props: any) {
    super();

    if (props.match.params.id == undefined) {
        this.state = {
            id: props.match.params.id,
            name: '', address: '', city: '',
            state: '', postalCode: '', phone: '',
            email: '',
            loading: false,
            redirect: false
        };
    }
    else {
        this.state = {
            id: props.match.params.id,
            name: '', address: '', city: '',
            state: '', postalCode: '', phone: '',
            email: '',
            loading: true,
            redirect: false
        };

        let contactService = new ContactService();
        contactService.getById(this.state.id)
            .then(data => {
                this.setState({
                    name: data.name, address: data.address, city: data.city,
                    state: data.state, postalCode: data.postalCode, phone: data.phone,
                    email: data.email,
                    loading: false
                });
            });
    }
}

Again, this is way more code than it would be the contact class were being used. Next, the render function needs to be adjusted to render the UI for the read-only view or the contact creation. The decision is based on the ID being set or undefined.

public render() {
    let contents = this.state.loading
        ? <p><em>Loading...</em></p>
        : this.state.id != undefined &&
          !this.state.redirect
            ? this.renderExistingContact()
            : this.renderNewContact();

    return <div>
        <h1>Contact Detail</h1>
        <hr />
        {contents}
        <NavLink to={'/contactlist'}>Back to List</NavLink>
        <hr />
    </div>;
}

The render of an existing contact needs to be changed to use state instead of an instance of a contact.

private renderExistingContact() {
    return <dl className="dl-horizontal">
        <dt>ID</dt>
        <dd>{this.state.id}</dd>
        <dt>Name</dt>
        <dd>{this.state.name}</dd>
        <dt>Address</dt>
        <dd>{this.state.address} {this.state.city}, {this.state.state} {this.state.postalCode}</dd>
        <dt>Phone</dt>
        <dd>{this.state.phone}</dd>
        <dt>Email</dt>
        <dd>{this.state.email}</dd>
    </dl>;
}

The following is the render of the add contact UI. I will call out a couple of parts after. The bulk of the code is just rending of the form.

    private renderNewContact() {
        return (
            <div>
                {this.state.redirect && <Redirect to={`/contactdetail/${this.state.id}`}/>}
                <form role="form" className="form-horizontal" onSubmit={(e: any) => this.handleSubmit(e)}>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Name</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="name" className="form-control" name="name" value={this.state.name} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Address</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="address" className="form-control" name="address" value={this.state.address} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">City</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="city" className="form-control" name="city" value={this.state.city} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">State</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="state" className="form-control" name="state" value={this.state.state} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Zip</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="zip" className="form-control" name="postalCode" value={this.state.postalCode} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Phone</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="phone" className="form-control" name="phone" value={this.state.phone} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Email</label>
                        <div className="col-sm-10">
                            <input type="email" placeholder="email" className="form-control" name="email" value={this.state.email} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="text-center">
                        <button className="btn btn-success btn-lg" type="submit">Save</button>
                        <button className="btn btn-danger btn-lg" onClick={() => this.reset()}>Reset</button>
                    </div >
                </form>
            </div>
        );
    }

The following is the input for the contact’s name.

<input type="text" placeholder="name" className="form-control" name="name" value={this.state.name} onChange={(e: any) => this.handleChange(e)} />

Since I decided to go with the controlled component route React will be responsible for being the source of truth, not the form its self. To accomplish this it is important that the input has a name and an onChange event handler set up. The following is the handleChange function which uses the name from the on change event to update the proper property in the component’s state.

private handleChange(event: any): void {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    this.setState({ [name]: value });
}

The following line is the reset button which will reset the state to blank out all the fields in the form.

<button className=”btn btn-danger btn-lg” onClick={() => this.reset()}>Reset</button>

The reset function just uses setState to blank out all the fields.

private reset() {
    this.setState({
        name: '', address: '', city: '',
        state: '', postalCode: '', phone: '',
        email: ''
    });
}

Not surprisingly the submit button triggers a submit of the form. What happens on submit is defined in the opening form tag.

<form role="form" className="form-horizontal" onSubmit={(e: any) => this.handleSubmit(e)}>

The handleSubmit function takes the contact information in state and uses it to create a new instance of a Contact which is then passed to the ContactService to be saved. The service returns a new contact object from the server and the ID is stored to state.

handleSubmit(event: any): void {
    event.preventDefault();

    let contact = new Contact();
    contact.name = this.state.name;
    contact.address = this.state.address;
    contact.city = this.state.city;
    contact.state = this.state.state;
    contact.postalCode = this.state.postalCode;
    contact.phone = this.state.phone;
    contact.email = this.state.email;

    let contactService = new ContactService();
    contactService.save(contact)
        .then(c => this.setState({ id: String(c.id) }));

    if (this.state.id) {
        this.setState({ redirect: true });
    }
}

If the server does return an ID for the new contact then the redirect is set to true. Then will case the following code to run in renderNewContact which will redirect the user back to the read-only view of the new contact.

{this.state.redirect && <Redirect to={`/contactdetail/${this.state.id}`}/>}

Wrapping Up

This pretty much gets the React application in line with the Aurelia and Angular sample applications. It has been fun getting a handle on the very, very basics of React. While I am back in the basics sample projects I may go ahead and tackle a Vue sample next.

The finished code can be found here.