User Service

This is a step-by-step walkthrough of creating a simple microservice using the Owin Framework.

This walkthrough assumes that you have already completed the Hello, World walkthrough. If you have not completed it yet I suggest you at least read through these instructions before continuing with this walkthrough.

This walkthrough builds a microservice with these features:

  • The microservice has an endpoint where you can POST a form to register a new user based on an email address and a password. Each user is allocated a unique ID.
  • The microservice will not allow the same email address to be registered twice.
  • The microservice has endpoints that you can POST a form to for logging in and logging out.
  • The microservice has an endpoint that returns the user ID for the logged in user (based on session cookie) or empty string if the user is not logged in.
  • The microservice has a very simple administration UI that shows a list of all of the registered users and whether they are currently logged in or not.

To keep down the scope of this walkthrough and concentrate on the aspects of how to build a microservice and not how to build a database, this solution will only retain data in memory. All of the registered users will be wiped each time the service is restarted.

Overview

The steps you will follow in this walktrhough are as follows:

  1. Create a new Visual Studio solution using the "ASP.Net Empty Web Site" project template
  2. Add the required Nuget packages to the solution
  3. Write a Startup.cs and a Package.cs file that configures all the libraries and wires everything up
  4. Define an interface to the data layer and create a test implementation that only saves data in memory
  5. Add a class that implements the user service endpoints
  6. Add a test page with forms for registration, login and log out just for testing
  7. Add an administration page that shows users and live updates when there are changes

This walktrhough differs from writing a real microservice in these ways:

  • The service would not normally implement the required business logic. This should be contained in a separate class and injected into the service implementation using IoC
  • The data layer would need to persist the data to a persistent storage mechanism such as an RDBMS, NOSQL or Graph Database.
  • All of the important classes should have unit tests. This is a much better approach than adding a test page because the tests are thorough, repeatable and can be automated.

Starting a new Visual Studio solution

The most educational way of doing this walkthrough is by starting from a completely empty project and add all of the code. You don't need to actually type the code, copy/paste will work just fine.

  1. In Visual Studio start a new project of type "ASP.NET Empty Web Site". This will create a project that contains very little. In Visual Studio 2019 the project template looks like this:
  2. Install these NuGet packages
    • Ioc.Modules.Ninject
    • Owin.Framework
    • Owin.Framework.Urchin
    • Owin.Framework.Pages.Core
    • Owin.Framework.Pages.Framework
    • Owin.Framework.Pages.Restful
    • Owin.Framework.Pages.Html
    • Microsoft.Owin.Host.SystemWeb

After installing, the Nuget Package Manager will look something like this (version numbers will vary depending on which .Net Framework version you chose)

There are many other Owin Framework NuGet packages that are not illustrated by this walkthrough.

Configure your project as an OWIN website hosted on IIS

Note that this step would be required for any OWIN application and is not specific to the Owin Framework.

The steps required here are the same as for the Hello, World walkthrough. Please refer back to it for more details.

Configure an IoC container

For this walkthrough we are going to use Ninject and Urchin, but you can use any IoC container and any configuration mechanism you choose. There is documentation you can read and copy code from, or you can copy from the hello world example

Read the documentation to figure out what code you need to add, or just copy the code below to get going quickly.

var packageLocator = new PackageLocator()
   .ProbeBinFolderAssemblies()
   .Add(Assembly.GetExecutingAssembly());
var ninject = new StandardKernel(new Ioc.Modules.Ninject.Module(packageLocator));

Set up the Owin Framework middleware pipeline

This is the first step in this walktrhough that is specific to the Owin Framework

You can review the documentation on Owin Framework configuration, or you can look at Startup.cs for a working example to copy from.

An example of the type of code you will need to add to your Startup class is shown below for reference. In a real application there are a number of ways that you can write this code. For this walkthrough you can copy/paste if you like.

var config = ninject.Get<IConfiguration>();

var pipelineBuilder = ninject.Get<IBuilder>();
pipelineBuilder.Register(ninject.Get<PagesMiddleware>()).ConfigureWith(config, "/pages");

app.UseBuilder(pipelineBuilder);

Configure the Pages middleware

In this section you will configure the Pages middleware to scan your compiled code and construct services and web pages from classes that are decorated with custom attributes.

  1. At the end of the Configuration method in your Startup class, resolve IFluentBuilder from your IoC container, for example
    var fluentBuilder = ninject.Get<IFluentBuilder>();
  2. Install the Restful service build engine so that we can define services by decorating classes with attributes. The code will be similar to this:
    ninject.Get<OwinFramework.Pages.Restful.BuildEngine>().Install(fluentBuilder);
  3. Install the Html element build engine so that we can add web pages to our microservice by decorating classes with attributes. The required code is similar to this:
    ninject.Get<OwinFramework.Pages.Html.BuildEngine>().Install(fluentBuilder);
  4. Get the fluent builder to scan your application assembly for classes that are decorated with attributes that makes them into definitions of serices, pages, regions, layouts etc. The fluent builder will use the build engines that we configured earlier. The extra line of code you need to add for this is:
    fluentBuilder.Register(Assembly.GetExecutingAssembly());
  5. Finally, after registering all of the elements of your website with the fluent builder you need to bind the elements together because they can reference each other by name, and they can be registered in any order. You will need this line of code to do this:
    ninject.Get<INameManager>().Bind();
If your microservice has no UI at all then you can skip the step of registering the html build engine, and you can remove the reference to the Owin.Framework.Pages.Html Nuget package.
Note that there are a number of Build Engines included in the base libraries, and you can also install third party Build Engines that provide alternate implementations. Build Engines are like factories that contruct and configure Services, Pages, Layouts, Regions, Components, Templates etc.

At this point your Startup.cs file should look something like this:

using Ioc.Modules;
using Microsoft.Owin;
using Ninject;
using Owin;
using OwinFramework.Builder;
using OwinFramework.Interfaces.Builder;
using OwinFramework.Pages.Core;
using OwinFramework.Pages.Core.Interfaces.Builder;
using OwinFramework.Pages.Core.Interfaces.Managers;
using System.Reflection;

[assembly: OwinStartup(typeof(Startup))]

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var packageLocator = new PackageLocator()
            .ProbeBinFolderAssemblies()
            .Add(Assembly.GetExecutingAssembly());

        var ninject = new StandardKernel(new Ioc.Modules.Ninject.Module(packageLocator));

        var config = ninject.Get();

        var pipelineBuilder = ninject.Get();
        pipelineBuilder.Register(ninject.Get()).ConfigureWith(config, "/pages");

        app.UseBuilder(pipelineBuilder);

        var fluentBuilder = ninject.Get();
        ninject.Get().Install(fluentBuilder);
        ninject.Get().Install(fluentBuilder);

        fluentBuilder.Register(Assembly.GetExecutingAssembly());

        ninject.Get().Bind();
    }
}
    

Adding service endpoints

There are many ways to add Restful service endpoints, but the easiest way is to add a class and decorate it with attributes, so lets do that. Go ahead and create a new class in the solution called HelloService.cs containing the following code:

    using OwinFramework.Pages.Core.Attributes;
    using OwinFramework.Pages.Core.Enums;
    using OwinFramework.Pages.Restful.Interfaces;
    
    [IsService("hello", Methods = new[] { Method.Get })]
    public class HelloService
    {
        [Endpoint(UrlPath = "/")]
        public void Hello(IEndpointRequest request)
        {
            request.Success("{message:'Hello, this is a microservice'}");
        }
    }

Now you can press F5 in Visual Studio and your microservice will handle the request and return a hello message.

Wow, that was easy!

All of your basic restful endpoints will be simple like this, but even the most complex scenarios are supported in a way that makes it as easy as possible to hide the complexity without sacrificing scaleability. See the Restful builder documentation for more information on how to take this up to the next level.

The only issue you are likely to encounter at this point is about version compatibility of Nuget packages. Most open source projects support a wide variety of frameworks and versions of .Net, but Microsoft's packages do not, and this leads to a situation where is can be quite hard to find a combination of Nuget package versions that work together.

The developers of the Owin Framework try to provide versions that are compatible with every version but Microsoft makes this very hard and there are many projects to update each time, so this is a far from ideal situation. We appologize if you experience Nuget version incompatibilities at this point in the walkthrough.

Building a dummy data layer

In order to see a few more features and look at an example closer to the real world we are going to need a bit more code. Go ahead and add a data layer interface definition and an implementation of it from the source code below, then add a Package.cs file so that IoC knows that this class is a singleton that implements the interface.

To keep this example simple we can add all of this to one source file, but in your application you would break this up into one source file per type.

DataLayer.cs

using Ioc.Modules;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

public enum RegisterResult
{
    Registered,
    PasswordTooSimple,
    DuplicateEmail,
    InvalidEmail
}

public class User
{
    [JsonProperty]
    public string Email { get; set; }

    [JsonIgnore]
    public string Password { get; set; }

    [JsonProperty]
    public string LoginToken { get; set; }
}

public interface IDataLayer
{
    RegisterResult Register(string email, string password);
    string Login(string email, string password);
    void Logout(string loginToken);
    User[] GetUsers();
}

[Package]
public class DataLayerPackage : IPackage
{
    public string Name => "Data layer";

    public IList IocRegistrations => new List
    {
        new IocRegistration().Init()
    };
}

public class DataLayer: IDataLayer
{
    private readonly object _lock = new object();
    private User[] _users = new User[0];

    public User[] GetUsers()
    {
        return _users;
    }

    public RegisterResult Register(string email, string password)
    {
        if (string.IsNullOrWhiteSpace(email) || email.Count(c => c == '@') != 1)
            return RegisterResult.InvalidEmail;

        if (string.IsNullOrWhiteSpace(password) || password.Length < 5)
            return RegisterResult.PasswordTooSimple;

        lock(_lock)
        {
            if (_users.FirstOrDefault(u => string.Equals(u.Email, email, StringComparison.OrdinalIgnoreCase)) != null)
                return RegisterResult.DuplicateEmail;

            var newUser = new User
            {
                Email = email,
                Password = password,
                LoginToken = Guid.NewGuid().ToString("n")
            };
            var users = _users.ToList();
            users.Add(newUser);
            _users = users.ToArray();
        }

        return RegisterResult.Registered;
    }

    public string Login(string email, string password)
    {
        var user = _users.FirstOrDefault(u => 
            string.Equals(u.Email, email, StringComparison.OrdinalIgnoreCase) &&
            u.Password == password);

        if (user == null) return string.Empty;

        if (user.LoginToken == null)
            user.LoginToken = Guid.NewGuid().ToString("n");

        return user.LoginToken;
    }

    public void Logout(string loginToken)
    {
        var user = _users.FirstOrDefault(u => u.LoginToken == loginToken);

        if (user != null)
            user.LoginToken = null;
    }
}

Adding the user service endpoints

We did this already for the hello service example. Lets repeat that again with the injected dependency on the data layer we just created. Because we now have an injected dependency, we will also have to provide our Niject container to the fluent builder so that it can build our service.

In your Startup.cs file, modify the line of code that registers our executing assembly with the fluent builder to get it to use Ninject to build our service. The modified line of code looks like this:

fluentBuilder.Register(Assembly.GetExecutingAssembly(), t => ninject.Get(t));

Now we can go ahead and add the service endpoints.

UserServiceEndpoints.cs

using OwinFramework.Pages.Core.Attributes;
using OwinFramework.Pages.Core.Enums;
using OwinFramework.Pages.Restful.Interfaces;
using OwinFramework.Pages.Restful.Parameters;

[IsService("user", BasePath = "/auth/", Methods = new[] { Method.Get, Method.Post })]
public class UserServiceEndpoints
{
    private readonly IDataLayer _dataLayer;

    public UserServiceEndpoints(IDataLayer dataLayer)
    {
        _dataLayer = dataLayer;
    }

    [Endpoint(Methods = new[] { Method.Get })]
    public void Users(IEndpointRequest request)
    {
        request.Success(_dataLayer.GetUsers());
    }

    [Endpoint(Methods = new[] { Method.Post })]
    [EndpointParameter("email", typeof(RequiredString), EndpointParameterType.FormField)]
    [EndpointParameter("password", typeof(RequiredString), EndpointParameterType.FormField)]
    public void Register(IEndpointRequest request)
    {
        var email = request.Parameter("email");
        var password = request.Parameter("password");

        request.Success(_dataLayer.Register(email, password));
    }

    [Endpoint(Methods = new[] { Method.Post })]
    [EndpointParameter("email", typeof(RequiredString), EndpointParameterType.FormField)]
    [EndpointParameter("password", typeof(RequiredString), EndpointParameterType.FormField)]
    public void Login(IEndpointRequest request)
    {
        var email = request.Parameter("email");
        var password = request.Parameter("password");

        request.Success(_dataLayer.Login(email, password));
    }

    [Endpoint(Methods = new[] { Method.Post })]
    [EndpointParameter("token", typeof(RequiredString), EndpointParameterType.QueryString)]
    public void Logout(IEndpointRequest request)
    {
        var token = request.Parameter("token");
        _dataLayer.Logout(token);
        request.Success();
    }
}    

At this point we have a fully functional microservice and you can test it using a tool like PostMan. Even though it is complete we will be adding more to it below to achieve the specification we were looking for. First of all lets explain this source file in detail.

The Users() method can be called with a GET request to the url /auth/users. This endpoint just calls the data layer and serializes the list of users to JSON. You can press F5 in Visual Studio and append /auth/users to the url and your service should return an empty array of users.

If you don't specify the url path for an endpoint it defaults to a concatenation of the BasePath property of the IsService attribute and the name of the endpoint method. If you do specify the url path of the endpoint you can either make it relative to the base path of the service by not starting with a leading forward slash, or absoulte within the website (as we did in the hello service example) by putting forward slash as the first character of the path.
You can serialize the response to other formats as well as JSON by explicitly specifying the serializer to use. When no serializer is specified JSON is the default. You can also specify the serializer on the service itself and optionally override this for each endpoint.

The Register() method must be called using a POST request, and takes two parameters. The EndpointParameter attributes define that these are form fields and that they must be non-empty strings. If you call this endpoint with invalid parameters then the Owin Framework will return a 400 response status with a validation message.

To read the parameters that were passed in, the Users() method calls the Parameter() method of the request passing the name of the parameter. The Owin Framework will parse the body of the POST as a form and extract the form field values, validate them and return the value to the appllication.

It is very important when retrieving endpoint parameters that the type specified in the call to the Parameter() method matches the type of data returned by the parameter validator. In this example we passed the typeof(RequiredString) as the parameter validator so we must retrieve the parameter value as string.

Adding some forms to test it

To fully test this we need to be able to POST some forms, and since some microservices have a user interface, we are going to go ahead and see an example of how we might easily add a user interface to our microservice. Note that this step is completely optional since many microservices do not have any UI. If you are using this tutorial as a stating point for your own microservice and you don't have any UI then I recommend removing the dependency on the Owin.Framework.Pages.Html Nuget package from your project.

In this case we can add some simple forms by adding an html file to our project and serving this as a static asset. We could do this with a service endpoint, but the Owin Framework Static Files middleware will do it much better.

To be completed ...

Adding a management UI

There are many choices available for creating a UI. The Pages part of the Owin Framework is designed to provide complete flexibility to cater to all the different kinds of website and web services that exist from a simple UI on a microservice to a website with millions of pages being served to billions of users. For the purpose of this walkthrough I have decided to show how to build pages where each page in a Vue.js view model defined by a JavaScript file and an html file, but this is by no means the only way to do this.

To be completed ...