Bootstrapping ASP.NET Core - Week 2

Creating a REST API with MVC and Swagger

In our last post we navigated around an empty ASP.NET Core application. This week we are going to start adding familiar MVC and WebAPI style routes and controllers. Along the way we will compare the starter template to the project we are building from scratch creating and discover how Swagger can help us easily document our REST API.

We will also dig into ASP.NET Core configuration options and managing different environments in our application.

Adding MVC

The empty application had a single response to every request using a single call to the app.Run middleware.

    app.Run( async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });

One of the best things about the new ASP.NET Core is that for a simple application there is very little cermenory required to get up and started. While app.Run is powerful and simple it is not something we would want to build our application on.

To work with our more familiar routes, controllers and views we add the Microsoft.AspNetCore.Mvc NuGet package reference.

PM> Install-Package Microsoft.AspNetCore.Mvc

Once the package is installed and we have accepted all the licenses we need to update our Startup.cs class to add MVC services to the ServicesCollection and wire up the MVC Middleware.

MVC Services


public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();
}
    

MVC Middleware


public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    app.UseMvc();

    app.Run( async (context) =>
    {
       await context.Response.WriteAsync("Hello World! I am the fallback middleware.");
    });
}

Next we will create a new Controllers folder and add a new HomeController class. We will use attribute based routing to wire up the default action and return a simple string result to start with.

[Route("/")]
public class HomeController: Controller
{
    [HttpGet]
    public IActionResult Index()
    {
        return Ok("hello from home index");
    }
}

Which gives us the following result

indexok

Awesome!

Creating Views

Without adding any other configuration data we can create the standard Views folder stucture with a layout and an index page and begin generating simple HTML responses.

views_folders

We can then update controller action to return the view.

[Route("/")]
public class HomeController: Controller
{
    [HttpGet]
    public IActionResult Index()
    {
        return View();
    }
}
These files are pretty boring. Check out the source code for part 2 for details of these files if you are not familiar with them.

Visual Studio 2017 Adding MVC Dependencies

I encountered a new prompt when trying to add the new controller class using the right click Add -> Controller action. This the first time I have been prompted to add packages when trying to create a new class. I'm guessing it's a new feature in Visual Studio 2017.

mvcdeps

I still want to add all the dependecies myself as I build out the applicaiton but out of curiosity I selected the __Full Dependencies__options to see what it would do. There were 5 packages, or group of packages, in addition to what we already had.

  • Microsoft.AspNetCore.StaticFiles - no surprise here, we will use this later to serve our CSS and Javascript files
  • Microsoft.EntityFrameworkCore.* - new entitity framework library for dotnet core, we will add this when we get to our data layer
  • Microsoft.Extensions.Logging.Debug - additional logging middleware, I'm guessing there are lots of options here
  • Microsoft.VisualStudio.Web.BrowserLink - not really familiar with this library but I believe it helps with auto updating browser windows during development
  • Microsoft.VisualStudio.Web.CodeGeneration.Design - enables some code gen tools in Visual Studio

Creating our Models

Before we jump into creating our REST API's we will create some POCO classes for the basic model we shared in the first post. I've included it here again for reference.

models

We will not be talking to a database for this post but we can use the Model classes for inputes and outputs of our REST API.

All the models are in the source code for this post and are similar to the Customer class below.

namespace Fido.Web.Models
{
    public class Customer: BaseModel
    {
        public String Name { get; set; }
        public String Email { get; set; }
        public String Phone { get; set; }
        public String Address { get; set; }
        public String City { get; set; }
        public String State { get; set; }
        public String Zip { get; set; }
        public String Notes { get; set; }
        public bool NotifyAdHoc { get; set; }
    }
}

Creating the REST API

The best feature, after being cross platform, is the consolidation of both MVC and WebAPI into a single, unified package in ASP.NET Core. Other than creating a separate folder to hold the controllers for our API both types of controllers will look and behave in the same way.

Before we get started we will create a BaseApiController class to help add some conventions to our API controllers. We will continue to use attribute routing so that we can define a default route in our base controller that exposes all of our API controllers at /api/[controllerName].

BaseApiController

[Route("/api/[controller]/")]
public abstract class BaseApiController: Controller
{
}

CustomersController

Child controllers can create their own routes but will inherit their routing from the parent class if no attributes are provided.

    public class CustomersController: BaseApiController
    {
        public IActionResult Get()
        {
            var customers = new List<Customer>()
            {
                new Customer(){Id = Guid.NewGuid(), Name = "Bob Smith"}
            };

            return Ok(customers);
        }
    }

Result

After firing up the application in Postman we get the following results.

postman_null

Looks great except all the null values. I prefer to not return the values at all if they are null. I would like to set this globally so we will dig a little deeper.

Json Serialization Settings

We know that ASP.NET Core is using Json.NET for serializing JSON values. When we added the MVC services we accepted the default configuration settings but we can also set additional options on the MVC services at the same time we add them to the ServicesCollection.

In this case the MVC framework provides an extension method AddJsonOptions that allows us to configure the JSON serialization options. After searching around for a bit we discover that we have to add the following code to ignore null values. While we are in this code section we will also ensure that we are using Camel Casing for our JSON responses.

public void ConfigureServices(IServiceCollection services)
{
     services.AddMvc()
         .AddJsonOptions(options => {
              options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
              options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
          });
}

After this update the response to the request now returns without the null property values.

postman_nonulls

I like that much cleaner.

Adding Configuration Files

The next step in our journey is to add some configuration data to our application. We'll need to be able to at least store a ConnectionString when we add a database to our project so let's get everything setup before we get there.

In previous versions of ASP.NET most of the configuration data was stored in the web.config file. ASP.NET Core introduces a new hierarchial name-value pair configuration system. Configuration data can be read from several file formats (JSON, INI, XML), the command line, environment variables, or a even a custom provider.

For our application we will use JSON files and Environment Variables for our configuration data. I know that we will want to use User Secrets when we add a database so we will add that as well while we are here.

First we need to add the Configuration NuGet packages.

  • Microsoft.Extensions.Options.ConfigurationExtensions
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.Configuration.EnvironmentVariables
  • Microsoft.Extensions.Configuration.UserSecrets

We wire up our application configuration in the constructor of Startup.cs and add a Configuration property so the rest of our startup methods can take advantage of the configuration.

public IConfigurationRoot Configuration { get; set; }

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
          .SetBasePath(env.ContentRootPath)
          .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
          .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
          .AddUserSecrets()
          .AddEnvironmentVariables();

    Configuration = builder.Build();
}

As we add configuration sources the values in later sources will override the values in the earlier sources. This will make our deployments easier and help us keep sensitive data out of source control. The following diagram provides a simple overview of the configuration settings hierarchy.

configuration_flow

Configuring Options

Once loaded there are a couple of ways to access configuration from the application. Through the Configuration property on our Startup class or by injecting classes representing the configuration data we care about directly into our controllers.

I prefer to break up my configuration data into strongly typed classes that I can inject into my controllers.

To explore injecting options we will create an AppSettings class in our project to represent the settings we care about, in this case just a title.

public class AppSettings
{
    public string Title { get; set; }
}

In our ConfigureServices method we can populate and make this class available to our controllers using the Configure<T>() extension on the ServiceCollection.

appsettings.json

{
  "appSettings": {
    "title": "Walk With Fido"
  }
}

Configuration Code

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.Configure<AppSettings>( Configuration.GetSection("appSettings"));
}

Controller
The settings can then be injected into the controller as an IOptions instance.

[Route("/api/settings")]
public class AppSettingsController: BaseApiController
{
    private readonly AppSettings _appSettings;

    public AppSettingsController(IOptions<AppSettings> _appSettingsAccessor)
    {
        _appSettings = _appSettingsAccessor.Value;
    }

    [HttpGet]
    public IActionResult Get()
    {
        return Ok(_appSettings);
    }

}

This allows us to access and return our app settings very easily.

settings_snap

In this case the application is pulling in the value from the appsettings.development.json file.

Swagger - REST API Documentation

I still do not want to pick a front end framework and thankfully we can still kick that decision down the road yet again and still provide a great way to exercise our REST API by using Swagger and a NuGet package called Swashbuckle.

If you are unfamiliar with Swagger jump on over to http://swagger.io/ and take a quick look around. Basically it is a schema for defining REST API's as a JSON document that client libraries can turn into working documentation and even test harnesses to exercise the API.

PM> Install-Package Microsoft.AspNetCore.Mvc

After adding the NuGet package to our project we walk through the steps we used when we added MVC. We simply add the Swagger services and then we add the Swagger middleware.

Swagger Services

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new Info { Title = "WalkFido API", Version = "v1" });

    //Set the comments path for the swagger json and ui.
    c.IncludeXmlComments(GetXmlCommentsPath(PlatformServices.Default.Application));
    c.DescribeAllEnumsAsStrings();
});

Swagger Middleware

For our application we will enable two Swagger middleware components. The first creates an enpoint that serves up the JSON definition file and the second creates a self hosted UI that will allow us to browse and exercise our API.

// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();

// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUi(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "WalkFido API V1");
});
Checkout the Swagger documentation for details on adding Swagger to our ASP.NET Core application at the project's GitHub page

XML Documentation

Swagger can make use of the XML documentation that we add to our project to create even more detailed documentation. From the Properties -> Build tab of our project we need to make sure the Xml documentation file checkbox is checked so that when the project is built an XML file is created. Once the file is created we need to set the properties of the generated file to copy the XML documentation to the output folder.

xmldocsetting

Swagger UI

Once that is all setup the application will generate a very nice looking documentation page at ~/swagger.

When we load up the UI page we see a list of all the controllers and the actions they support, along with any documentation we provided and forms for sending requests to the API.

swaggerui_list

We can drill into each action and send a request to exercise the API. At this point I'm not sure if this tool is better than using Postman for really testing my API but I really like that it is auto generated.

swaggerui_ui

And here is the code and XML comments that generated the Swagger details above.

/// <summary>
/// Get Attachment contents
/// </summary>
/// <remarks>
/// Can be called with or without the attachment file name even though Swagger indicates that it is required.
/// 
///     GET /api/customers/41E864E1-9BC9-440A-8209-FF42D29445C4/attachments/D298F1A4-A88D-4FEC-B966-AD062EEDFE78
///     GET /api/customers/41E864E1-9BC9-440A-8209-FF42D29445C4/attachments/D298F1A4-A88D-4FEC-B966-AD062EEDFE78/myimpage.png
///     
/// </remarks>
/// <param name="id">Id of the attachment</param>
/// <param name="entityType">Type of collection the owning entity belongs to</param>
/// <param name="entityId">Id of owning object</param>
/// <param name="name">File name of the image</param>
/// <returns>The contents of the attachment</returns>
/// <response code="200">Returns contents of the attachment</response>
/// <response code="404">If the id is not found</response>
[HttpGet("/api/{entityType}/{entityId}/attachments/{id}/{name?}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public IActionResult Get(Guid id, string entityType, Guid entityId, [FromRoute]string name = null)
{
    return Ok(new { id, entityId, entityType, name });
}

Environment Based Configuration

As soon as I wired up the Swagger UI I asked myself,

"Do I really want the API documentation and forms available on my production web site?"

From some applications we would want this but I don't think we will for this application, so we need to know how to turn it off. Luckily for us ASP.NET Core makes it very easy to accomplish with only a few code changes.

If we review our Startup.cs class we will find that we are already referencing our environment in a couple of places.

First when we add configuration settings in the constructor

.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)

and again in our Configure method when we setup the developer exception page middleware

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

We are already using the env.IsDevelopment() call in the ConfigureMethod so let's continue that pattern for adding the Swagger middleware and register it after we register the exception page

if (env.IsDevelopment())
{   
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUi(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "WalkFido API V1");
    });
}
Remember that the request pipeline is processed in the order that the middleware is added during the Configure method. For now we are okay adding Swagger before any other middleware but that may change down the road.

Configure{env.EnvironmentName}Services

The ASP.NET Core Startup class supports a convention that allows us to add environment specific ConfigureServices methods. One method is called when we are in development, another in test and yet another in production.

For now we only want to register Swagger in the development environment. To accomplish that we can create a new method call ConfigureDevelopmentServices and only register the Swagger services when that method is called.

public void ConfigureDevelopmentServices(IServiceCollection services)
{
    ConfigureServices(services);
    
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "WalkFido API", Version = "v1" });
        c.IncludeXmlComments(GetXmlCommentsPath(PlatformServices.Default.Application));
        c.DescribeAllEnumsAsStrings();
    });
}

The ConfigureServices method will continue to serve as a fallback whenever the application is run in an environment that we have not created a custom ConfigureServices method for.

Next Steps

With the API definition, MVC and configuration support we are starting to look like a real application. The source code and demo site are available at the following locations.


For now I've set the Azure site to run as development so the Swagger pages show up

The next posts will cover adding data persistence using Entity Framework Core. After that we wil tackle Authentication and Authorization and then finally pick a front end framework and work on the UI. I've been avoiding the front end but the way ASP.NET Core approaches front end development is actually one of my favorite improvements in this version of ASP.NET.

boisecodecamp

Also, don't forget to register for Boise Code Camp or come and present a session yourself.