This post is a continuation in a series about building a full stack web application. Previous posts in the series:
Enough about data and the framework, let’s take a look at the Stocks Tracker API, and see how information from previous posts have gotten us to this point.
The Stocks Tracker API needs to provide data to a variety of consumers, in a REST-ful way, accessible via the HTTP protocol. Additionally, the API needs to be testable from HTTP tools such as Fiddler, cURL and Postman.
Goals
I had some goals to accomplish with the API project in Visual Studio. First, include only packages and code necessary to get the API running. That meant not including any assemblies from the MVC framework, which Visual Studio ASP.NET project templates include by default for documentation purposes, but cause project bloat, and deployment of “dead code.” Besides, a tool like Swagger provides a simple and lightweight way to document your API, without turning it into a web site.
Another goal was to leverage the relationships ASP.NET Identity has with Entity Framework and OWIN/Katana to provide authentication & authorization functionality for the API. The API needs to grant resource access only to those users properly authenticated and authorized by Identity.
Finally, I wanted to leverage existing financial APIs that provide quotes and news items for stocks and other securities.
Startup & Configuration
I started by adding a new class library project to the Stocks Tracker solution, named StocksTracker.API (lest there be any confusion as to what this project provides), followed by Global.asax and OWIN Startup classes from the project-level context menu. These classes allow us hooks into an application’s startup process, so that we can configure the application appropriately.
In Global.asax, the mighty Autofac is configured to register dependencies in its DI container. Autofac has a nice extension method that allows registration of all IHttpController implementations in a single line of code. This is also where we configure the database connection from values in the Web.config file (these values are overridden during the deployment phase). To keep things tidy, I also register any classes that the authorization middleware will need to communicate with the application.
// create IoC container
var builder = new ContainerBuilder();
// register all API controllers
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// register stock tracker context as singleton
builder.Register(pcf => new StocksTrackerContextFactory(sqlConnectionBuilder, false))
.As<IObjectFactory<StocksTrackerContext>>()
.SingleInstance();
// register objects used in user authentication/authorization
builder.Register(provider => new ApplicationOAuthProvider(
ConfigurationManager.AppSettings["Identity.PublicClientId"],
_container.Resolve<IUserAdministration>()))
.As<IOAuthAuthorizationServerProvider>()
.SingleInstance();
// register object that communicates between application and middleware authorization server
builder.Register(options => new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(ConfigurationManager.AppSettings["Identity.TokenEndpointPath"]),
AuthorizeEndpointPath = new PathString(ConfigurationManager.AppSettings["Identity.AuthorizeEndpointPath"]),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(days),
AllowInsecureHttp = true,
Provider = _container.Resolve<IOAuthAuthorizationServerProvider>()
})
.As<OAuthAuthorizationServerOptions>()
.SingleInstance();
ASP.NET allows global configuration via the GlobalConfiguration static class. Here is where we set Autofac as the default dependency resolver, and register any routes the API will expose (routing in Web API and MVC is an art unto itself. I prefer to use class- and method-level attributes to define application routes, instead of using the template collection, which is fragile and not intuitive).
Set Dependency Resolver:
// build container, and set dependency resolver
_container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(_container);
Define Routes:
// build container, and set dependency resolver
GlobalConfiguration.Configure(WebApiConfig.Register); // in Global.asax.cs
config.MapHttpAttributeRoutes(); // in WebApiConfig.Register
Startup.cs is where ASP.NET recommends adding middleware to an application, and Stocks Tracker is no different. OWIN passes an IAppBuilder object to the Startup class, and it’s here that the OWIN “pipeline” is constructed by adding modules to the builder. Each module is constructed in such a way that it either processes the incoming HTTP request, or passes control to the next module until one or none have processed the request. Besides OWIN middleware, there are many third-party modules available via NuGet, plus it’s easy to write your own custom middleware.
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user...
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// ...and to use a cookie to temporarily store information about a user logging in with a third party provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
Authentication/Authorization
As seen above, the API is now configured with OWIN middleware that will a) allow authentication of a registered user, storing the user’s credentials in a browser cookie; b) allow authentication of an external user, storing external access information in a browser cookie; and, c) require OAuth bearer tokens for access to restricted resources.
OAuth allows users access to our resources, provided they are authenticated by a third party such as Google, Twitter, Microsoft or Facebook. OWIN middleware decouples all of this from the application, reducing the process to a client/server interaction, with our API acting as the client, and OWIN in the server role. OWIN provides a wide range of configuration options, which are then passed to the middleware.
Here is a simple OWIN configuration example from the Stocks Tracker API: Create an instance of OAuthAuthorizationServerOptions to populate the OWIN OAuth middleware server with some default values. Include a custom provider derived from OAuthAuthorizationServerProvider to listen for and act on messages from the server. The server options instance is passed to the middleware by the UseOAuthBearerTokens() method during startup.
Pretty complicated stuff, but worth a deep dive, especially since OAuth is the standard for third party website authorization.
Services
In the last post we explored the Framework project of interfaces and POCOs, and the API is where they get put to work. Basically, each service is an implementation of a Framework interface, and each is registered with Autofac in the configuration phase for dependency injection. The methods do little more than query Entity Framework for data, given the criteria passed to the method, and return collections of POCO objects for the API to serialize for its callers.
public class StockTrackersService : IStockTrackers
{
// constructor with dependencies injected
public StockTrackersService()
public async Task<StockTrackerRecord> GetStockTrackerAsync(int stockTrackerId)
{
using (var ctx = _stocksTrackerContextFactory.GetObject())
{
// take advantage of async/await
return await ctx.StockTrackers
.SingleOrDefaultAsync(tracker => tracker.StockTrackerId == stockTrackerId);
}
}
}
The UserAdministration service, however, is declared entirely within the API project, so as not to add an unnecessary Identity dependency to Framework. Most examples and project templates instantiate Identity’s UserManager, UserStore, RoleManager and RoleStore classes all over the place, so UserAdministration is a nice facade to isolate and expose Identity functionality through a common interface.
Of note are three services that rely on third party APIs for quote, headline and chart data. These services provide wrappers around the third party APIs, so that the resources are consumed in a manner consistent with the EF services. They are all public APIs from Yahoo! Finance:
Quotes
Headlines:
Charts:
Cool.
Controllers
By convention, an ASP.NET Web API controller defines methods that correspond to an HTTP request by returning an appropriate IHttpActionResult implementation. The Stocks Tracker API will do the same, with a controller for each service, providing methods for each HTTP verb (GET, POST, PUT, DELETE, etc.) necessary.
The Stocks Tracker controllers will take advantage of the wide range of helper methods and properties provided by the abstract ApiController base class, which include helpers for data validation, IHttpActionResults and users. Each method will take advantage of .NET’s async/await functionality, which will help keep the API responsive.
These controllers are really the culmination of all the work up to this point: The routing engine routes an authenticated HTTP request to the appropriate API controller, which responds by returning data retrieved from a service call, or an appropriate error response. StockTrackersController is a good example, as it has an action method for each of the major HTTP verbs, has its routing and authorization controlled declaratively, and has its dependencies injected by the global dependency resolver.
[Authorize] <- attribute enforces only authorized access to this controller's resources
[RoutePrefix("api/StockTrackers")] <- attribute defines the base route for this controller's actions
public class StockTrackersController : ApiController
{
// this controller's service dependency
private readonly IStockTrackers _stockTrackers;
// constructor injection
public StockTrackersController(IStockTrackers stockTrackers)
{
_stockTrackers = stockTrackers;
}
// GET api/StocksTrackers <- the route for this GET action
[HttpGet] <- attribute enforces HTTP GET requests only
[Route] <- attribute defines this route as the base route
public async Task<IHttpActionResult> Get()
{
// ask the service for the user's stock trackers
var stockTrackers = await _stockTrackers.GetStockTrackersForUser(User.Identity.GetUserId());
// return an Ok action result (HTTP response 200) with data
return Ok(stockTrackers.Select(MapStockTrackerRecordToObject));
}
// GET api/StockTrackers/1 <- the route for this GET action
[HttpGet]
[Route("{id:int}", Name = GetStockTrackerRouteName)] <- attribute defines base route plus mandatory parameter type
public async Task<IHttpActionResult> Get(int id)
{
// return BadRequest action result (HTTP response 400)
if (id == 0)
return BadRequest();
// return Ok action result with data
// or NotFound (HTTP response 404)
}
// POST api/StockTrackers/Create <- the route for this POST action
[Route("Create")] <- attribute defines base route plus "Create"
[HttpPost] <- attribute enforces HTTP POST requests only
public async Task<IHttpActionResult> Create(StockTrackerModel model)
{
// return BadRequest action result if model fails validation
if (!ModelState.IsValid)
return BadRequest(ModelState);
// return Created action (HTTP response 201) with new data
var newStockTracker = await _stockTrackers.AddStockTrackerForUserAsync(model0;
return Created(newStockTracker);
}
// DELETE api/StockTrackers/1 <- the route for this DELETE action
[Route("{id:int}")] <- attribute defines base route plus mandatory parameter type
[HttpDelete] <- attribute enforces HTTP DELETE requests only
public async Task<IHttpActionResult> Delete(int id)
{
// delete and return empty Ok action result
await _stockTrackers.RemoveStockTrackerAsync(id);
return Ok();
}
// PUT api/StockTrackers/1 <- the route for this PUT or PATCH action
[HttpPatch] <- attribute enforces PATCH requests
[HttpPut] <- attribute enforces PUT requests
[Route("{id:int}")] <- attribute defines base route plus mandatory parameter type
public async Task<IHttpActionResult> Put(int id, UpdateStockTrackerModel model)
{
// update and return Ok result with data
var stockTracker = await _stockTrackers.UpdateStockTrackerAsync(model);
return Ok(stockTracker);
}
}
It’s worth noting the roles Identity and OWIN play behind the scenes with API controllers. Any route that is decorated with an Authorize attribute requires
authentication and authorization of the request. This is done is by looking for an access token in the HTTP Authorization header (OWIN), and attempting to match the token to a user (Identity via Entity Framework). An authentication or authorization failure will result in a 401 Unauthorized response code returned to the caller. Decorating a method with an AllowAnonymous attribute allows unauthorized access to the resource, provided the route is a match.
Testing
Sure, I had a nice API now, but how was I going to test it without a UI? Thanks to www.asp.net (a HUGE influence on this project, and an invaluable resource) and Pluralsight (ditto) I checked out Fiddler, which allowed me to send test HTTP requests to my API. Using examples from these resources I was able to register a user with the API, request an access token, then use the token to request access to restricted resources. And just like that, my li’l API was returning me data! Hot dog.
Summary
So now I have a working API able to perform CRUD actions in a REST-ful manner via HTTP. In theory this API should be able to provide data to any number or clients including the web/mobile web (ASP.NET MVC, ASP.NET Web Forms, AngularJS, Aurelia, KnockoutJS, ES2015), hybrid apps (HTML/JavaScript/CSS in a view, wrapped by native APIs), or purely native apps for Anroid iOS or UWP.
Oh, and one other thing: Converting all this to ASP.NET Core will be almost a total rewrite. Oh, well.
Up next: a UI!