Build real web app using .NET Core, Angular 5 and Template Stack templates (Part 1 - template review)

  This first part of the article aims to familiarize you with the.Net Core and Angular templates of Template Stack, and what is already done and ready to be used. The next (Part 2 - implement Todo app) part contains the implementaion logic of sample Todo application.
  If you want to download the final version the the Todo app check here.
 If you are familiar with downloading and runnning a .NET Core project from Template Stack you can start immediately, if not you can check how to set up the project using npm, gulp and visaul studio. (takes 5 minutes)

 Creating real web application with new technologies like .Net Core and Angular 5 from scratch could be challenging task. You should be awered of all the web develepment components and initial setups which are usually done by the most advanced developers in the team.
 This is why it's good idea to have some easy to use and configure boilerplate template. Some developers use the Visual Stuido templates, some have their own templates which are used to. These solution have some serious shortcomings like for example VS templates usually consists of one single project and everything is put in there. If you want to have good separation of concerns you have to put each logically separable part in different project and you will lose time for that.
 If you choose your own project template usually it gets messy during the renaming of the projects, solution and the relations. It's possible that one can be good at it and do it fast, but still you wouldn't be faster than Template Stack, which does all that in 30 seconds.
Template stack gives you great number of improvements which otherwise could take significant amount of time and effords. It gives you:

  • Proven project arhchitecture (or so called sepration of concerns) view here. It's written that it is MVC 5, but the architecture of the .NET Core templates is quite similar
  • Entity framework 'Code first' extented database models view here. This feature just applies some OOP principles over the database models and automatically handles them. For example it automatically sets the CreatedOn date and etc.
  • Implementation of Repository pattern to make the database access a bit more more abstract and easy.
  • Automapper extended use view here. Simplifies the work with the famous library Automapper
  • Backend token auhtentication.
      It consists of Register functionallity which is quite trivial and it's located in the AccountController and uses the default .Net Core user creation.
    The more interesting part is the Login functionallity which uses custom implemented Baerer Token Provider which is done by creating TokenProviderMiddleware, TokenProviderOptions, TokenProviderExtensions classes. The last one extends the IApplicationBuilder class and sets the middleware to it.
    Check in GitHub the complete implementation
    TokenProviderMiddleware
    
     public class TokenProviderMiddleware
        {
            private readonly RequestDelegate next;
            private readonly TokenProviderOptions options;
            private readonly Func> principalResolver;
    
            public TokenProviderMiddleware(
                RequestDelegate next,
                IOptions options,
                Func> principalResolver)
            {
                this.next = next;
                this.options = options.Value;
                this.principalResolver = principalResolver;
            }
    
            public Task Invoke(HttpContext context)
            {
                if (!context.Request.Path.Equals(this.options.Path, StringComparison.Ordinal))
                {
                    return this.next(context);
                }
    
                if (context.Request.Method.Equals("POST") && context.Request.HasFormContentType)
                {
                    return this.GenerateToken(context);
                }
    
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                return context.Response.WriteAsync("Bad request");
            }
    
            private static int GetClaimIndex(IList claims, string type)
            {
                for (var i = 0; i < claims.Count; i++)
                {
                    if (claims[i].Type == type)
                    {
                        return i;
                    }
                }
    
                return -1;
            }
    
            private async Task GenerateToken(HttpContext context)
            {
                var principal = await this.principalResolver(context);
                if (principal == null)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                    await context.Response.WriteAsync("Invalid email or password.");
                    return;
                }
    
                var now = DateTime.UtcNow;
                var unixTimeSeconds = (long)Math.Round(
                    (now.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
    
                var existingClaims = principal.Claims.ToList();
    
                var systemClaims = new List
                {
                    new Claim(JwtRegisteredClaimNames.Sub, principal.Identity.Name),
                    new Claim(JwtRegisteredClaimNames.Jti, await this.options.NonceGenerator()),
                    new Claim(JwtRegisteredClaimNames.Iat, unixTimeSeconds.ToString(), ClaimValueTypes.Integer64)
                };
    
                foreach (var systemClaim in systemClaims)
                {
                    var existingClaimIndex = GetClaimIndex(existingClaims, systemClaim.Type);
                    if (existingClaimIndex < 0)
                    {
                        existingClaims.Add(systemClaim);
                    }
                    else
                    {
                        existingClaims[existingClaimIndex] = systemClaim;
                    }
                }
    
                var jwt = new JwtSecurityToken(
                    issuer: this.options.Issuer,
                    audience: this.options.Audience,
                    claims: existingClaims,
                    notBefore: now,
                    expires: now.Add(this.options.Expiration),
                    signingCredentials: this.options.SigningCredentials);
    
                var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
    
                var response = new
                {
                    access_token = encodedJwt,
                    expires_in = (int)this.options.Expiration.TotalMilliseconds,
                    roles = existingClaims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value)
                };
    
                context.Response.ContentType = GlobalConstants.JsonContentType;
                await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
            }
        }
    
    TokenProviderOptions
    
     public class TokenProviderOptions
        {
            public string Path { get; set; } = "/token";
    
            public string Issuer { get; set; }
    
            public string Audience { get; set; }
    
            public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(15);
    
            public Func> NonceGenerator { get; set; } = () => Task.FromResult(Guid.NewGuid().ToString());
    
            public SigningCredentials SigningCredentials { get; set; }
        }
    
    TokenProviderExtensions
    
    
        public static class TokenProviderExtensions
        {
            public static void UseJwtBearerTokens(
                this IApplicationBuilder app,
                IOptions options,
                Func> principalResolver)
            {
                ValidateArgs(app, options, principalResolver);
    
                app.UseMiddleware(options, principalResolver);
    
                app.UseAuthentication();
            }
    
            private static void ValidateArgs(
                IApplicationBuilder app,
                IOptions options,
                Func> principalResolver)
            {
                if (app == null)
                {
                    throw new ArgumentNullException(nameof(app));
                }
    
                if (options?.Value == null)
                {
                    throw new ArgumentNullException(nameof(options));
                }
    
                if (principalResolver == null)
                {
                    throw new ArgumentNullException(nameof(principalResolver));
                }
            }
        }
    

    All of these are used in the Startup class.
  • Angular client which is located in the app folder of the Web Project of the template. The client uses TypeScript , systemjs for module loader and gulp for tasks automation like moving files, compiling the TypeScript and Sass or Less, setting up the node packages, bundling and minifing and etc.
    There is sample architecture included which starts with AppComponent and it's loading the home component by default. The client also has already implemented baerer authentication. The implementation consists of Account Module with it's own routing and a couple of components for login and registration. The routing implementation:
    account.routes.ts sets the path to the components
    
    
     import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    import {
        AccountComponent,
        LoginComponent,
        RegisterComponent
    } from './index';
    
    import { AuthNoGuardService } from '../../services/index';
    
    const ACCOUNT_ROUTES: Routes = [
        {
            path: '',
            component: AccountComponent,
            canActivate: [AuthNoGuardService],
            canActivateChild: [AuthNoGuardService],
            children: [
                { path: '', redirectTo: '/account/login', pathMatch: 'full' },
    
                { path: 'login', component: LoginComponent },
                { path: 'register', component: RegisterComponent }
            ]
        }
    ];
    
    @NgModule({
        imports: [RouterModule.forChild(ACCOUNT_ROUTES)],
        exports: [RouterModule]
    })
    
    export class AccountRoutingModule { }
    
      Here is also example of the use of so called Guards which are intented to verify (only cliendside) if the current user has certain authorization rights. In our case AuthNoGuardService checks if the user is registered or not and if it's registered it's doesn't allow him to go login or register.
      It worth mentioning the implementation of the service (as you see there is also client side service) which is responsible for the authentication. This AuthService does the actual queries to our API backend which we have previously set up.
    Also the AuthService keeps track of current authentication state of the user through BehaviorSubject which is an object that has certain behavior for example it can say rather the current user is authorized or not.
    Inside this service is used the IdentityService which parses the information from the login response and gets the email, the token, the expiration date and the roles of the current user which are stored locally in the localStorage of the user's browser.
If you feel confident with this first part you can continue with next (Part 2 - implement Todo app) where we are building on top of currently discussed things. If you want to download the final version the the Todo app check here.