Thursday 12 July 2018

How To Design A .Net Core + Angular 5 Web App From The Ground Up

When I first became interested in .Net Core + Angular, despite my knowledge in C#, Javascript, and Web Apps, I got a little bit lost. The technology is quite new and there is not always enough documentation to grasp the whole picture.
So, I went with templates included or downloaded from Visual Studio but was not satisfied with it. As I knew from experience, those ready-to-use solutions are great to give you an overview and some examples. But when you are looking for a specific solution, they just won’t do. Usually, when you need to customize things, they will fall apart.
Notes
  • It will not exactly be from the ground up, as you will use some basic templates. But it will give you the opportunity to build your app brick by brick and to fully understand what you are doing.
  • This is not a tutorial about .Net Core, Angular, or C#. These already exist and are well made:

  • This app will cover two fronts actually: one for the public made with Angular, the other one is the administration area built with NET Core MVC
  • For each step, I’ll try to give the click by click procedure and the dotnet commands as there are pros & cons with both.
My needs are very common:
  • An MVC architecture
  • An admin area
  • Scalability: Having several projects in a solution makes it easier when you want to spread it across different hosts
  • A database
Architecture
To spoil the surprise, this is what I did:
  • Class Library for the Model
  • Angular project for the View
  • RESTFul Web app project for the Controller
  • NET Core MVC project for the Administration area (basic CRUD interface)
By having separate processes, you will also build way faster! Starting an Angular front takes more than 10 seconds, so if you are working on your API and haven’t touched the front, you will waste time on every debug.
Every time you want to add a service, you just have to create a new project. Imagine you want to add sign-ins to Google, Facebook, Github … You make a new .Net Core “Class Library” project called “Connexions” and put all the related configs and wrappers in it.
This modular architecture will allow you to manage big projects in the long run. If one day, you realize that the admin part could be done completely differently, you just unplug it and create a new project.
This is the structure with Visual Studio’s template types:
Model (Class Library)
  • Models: classes representing tables in the database
  • DbContext: The hub for DbSets, repos, database configuration.
View (Website)
The Angular project
Controller (Web API)
  • ViewModels
    Classes used to format data when sending and receiving with Angular. Very useful with forms, you will put some validation rules here.
  • ControllersControllers exchanging data with Angular (RESTFul format)
Admin (Web App MVC)
  • Views
    The cshtml files used for the admin area
  • Controllers
    MVC Controllers for CRUD actions
Development
What you need:
For the sake of KISS, we will use an SQLite database, so you don’t have to install anything for that.
Create your solution -> Project
Open a command prompt in the folder of your choice:
  1. mkdir Project  
  2. cd Project  
  3. dotnet new sln -n Project  
Or
  • File > New > Project
  • In the left pane, Installed > Other Project Types > Visual Studio Solutions
  • In the middle pane, select Blank Solution.
  • Set the Name (here I’ll call it Project) and Location values for your solution, then click OK.
Create the Admin -> Project.Admin
Here, you SHOULD use dotnet CLI as it creates a project with SQLite by default and it’s very convenient.
Commands
  1. dotnet new mvc -n Project.Admin -au Individual  
  2. dotnet sln add Project.Admin/Project.Admin.csproj  
 As we want to share the same DB between projects, go to appsettings.json and replace DataSource=app.db by DataSource=..\\app.db
Or
  • Right-click on the solution in the solution explorer, then Add > Project.
  • In Visual C# > .NET Core, select NET Core Web Application and set the name (Project.Admin).
  • A pop-up will open. Select Web Application MVC and Change Authentication to Individual User Accounts (more here) then click Ok.

    .NET Core
  • Change json to "DefaultConnection": "DataSource=..\\app.db"
  • In Startup.cs, replace options.UseSqlServer by options.UseSqlite
At that point, you can press F5 and test your app. If you try to register, you will probably end up with this:
.NET Core
It’s because you changed the DB’s path, so it can’t find it. Just follow the instructions and refresh. It’s going to use the files in Data/Migrations to setup your DB. (command is: dotnet ef database update)
Because it’s a back-office, you want to protect it with a login:
  • Register to your app if it’s not done yet
  • Add [Authorize] just before your HomeController class declaration
  1. [Authorize]  
  2. public class HomeController : Controller  
With this, you won’t be able to access the home page without being logged in.
Create the DAL -> Project.DAL
In the main folder (Project) type,
  1. dotnet new classlib -n Project.DAL  
  2. dotnet sln add Project.DAL/Project.DAL.csproj  
/!\ If your solution is opened in VS 2017, you need to close the solution before and reopen it to see the changes.
Or
  • Right-click on the solution in the solution explorer, then Add > Project
  • In Visual C# > .NET Core, select Class Library, then Ok
Create a folder Models
Create a public class Product.cs in it
Add those properties.
  1. public Guid Id { get; set; }  
  2. public string Name { get; set; }  
  3. public string Description { get; set; }  
In the root folder, create the DbContext like so:
  1. using Microsoft.EntityFrameworkCore;  
  2. using Project.DAL.Models;  
  3.   
  4. namespace Project.DAL  
  5. {  
  6.     class ProjectDbContext : DbContext  
  7.     {  
  8.         public ProjectDbContext(DbContextOptions<ProjectDbContext> options) : base(options)  
  9.         {  
  10.         }  
  11.   
  12.         public DbSet<Product> Product { get; set; }  
  13.     }  
  14. }  
Add the EntityFramework reference to your Project.DAL.csproj file
  1. <ItemGroup>  
  2.     <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.1" />  
  3. </ItemGroup>  
Scaffold the controller in the admin project to manage the table
  • In Admin, right-click on the Controllers folder then Add Controller 
  • Choose MVC Controller with views, using Entity Framework then click Add

    .NET Core
  • Set the options like this:

    .NET Core
Or in the Project.Admin folder,
  1. dotnet aspnet-codegenerator controller -name ProductController -m Project.DAL.Models.Product -dc ApplicationDbContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries  
Then create the table in your DB:
  1. dotnet ef migrations add AddProductTable  
  2. dotnet ef database update  
/!\ If the database does not want to update, it's probably because of SQLite. So delete the app.db file and everything in the Migrations folder, then retry the above command. 
 
Build and test your app by going to /product 
Create a few products as we are going to need them later.
.NET Core
Create the API -> Project.API
In the main folder (Project) type
  1. dotnet new webapi -n Project.API --use-launch-settings  
  2. dotnet sln add Project.API/Project.API.csproj  
Or
  • Right-click on the solution in the solution explorer, then Add > Project
  • In Visual C# > .NET Core, select NET Core Web Application and set the name (Project.API)
  • A pop-up will open. Select Web API then click Ok
In Project.API.csproj add those lines to their related places.
  1. <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />  
  2. <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />  
Then dotnet restore to install them.
Add a reference to your DAL:
  1. dotnet add reference ..\Project.DAL\Project.DAL.csproj  
Or
  • Right-Click on API / Dependencies Add References
  • Check DAL then OK
Add the connection string to the appsettings.json
  1. {  
  2.   "ConnectionStrings": {  
  3.     "DefaultConnection""DataSource=..\\app.db"  
  4.   },  
  5.   "Logging": {  
  6.     "IncludeScopes"false,  
  7.     "LogLevel": {  
  8.       "Default""Warning"  
  9.     }  
  10.   }  
  11. }  
And in Startup.cs, add the DbContext
  1.         public void ConfigureServices(IServiceCollection services)  
  2.         {  
  3.             services.AddDbContext<ProjectDbContext>(options =>  
  4.                 options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));  
  5.   
  6.             services.AddCors();  
  7.   
  8.             services.AddMvc();  
  9.         } 
  • We also added the Cors so Angular can access your API
Scaffold the API controller
  • In API, right-click on the Controllers folder then Add Controller 
  • Choose API Controller with actions, using Entity Framework then Add

    .NET Core
  • Set the options like this:

    .NET Core
Or in the Project.API folder
dotnet aspnet-codegenerator controller -api -name ProductController -m Project.DAL.Models.Product -dc Project.DAL.ProjectDbContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
Build your app and go to /api/product - You should see something like this:
.NET Core

Create the front: Project.Angular
To install Angular, make sure the CLI is installed
  1. npm install -g @angular/cli  
Then create the project in your Project folder and test it:
  1. ng new Project.Angular  
  2. cd Project.Angular  
  3. ng serve -o  
Your browser should open http://localhost:4200 and show your Angular app.
Add it to your solution.
/!\ Attention: There is a bug in VS 2017 (see here)
  • Right-click on the solution, then Add > Existing Website
  • Select the folder Angular > Ok
If it’s not working, the only solution is to add it manually to the .sln file (I know: ugly!)
  1. Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Project.Angular""Project.Angular\", "{19A933CF-623B-4F15-AC94-74F308C4B148}"   
  2. EndProject  
You should now have something like this:
.NET Core
Now, create a product class and a product component:
  • Create the Model -> src/app/models/product.ts with this in it,
    1. export class Product {  
    2.   id: string;  
    3.   name: string;  
    4.   description: string;  
    5. }  
You can use this wonderful tool (QuickType) to transform your JSON result directly to TypeScript.
  • Create the component with:
    ng generate component components/product
  • Create the service like this:
    1. import { Injectable } from '@angular/core';  
    2. import { Observable } from 'rxjs/Observable';  
    3. import { of } from 'rxjs/observable/of';  
    4. import { Product } from './../models/product';  
    5. import { HttpClient, HttpHeaders } from '@angular/common/http';  
    6.   
    7. const httpOptions = {  
    8.   headers: new HttpHeaders({ 'Content-Type''application/json' })  
    9. };  
    10.   
    11. @Injectable()  
    12. export class ProductService {  
    13.   
    14.   private productUrl = 'http://localhost:31977/api/product';  
    15.   
    16.   constructor(private http: HttpClient) { }  
    17.   
    18.   getProducts(): Observable<Product[]> {  
    19.     return this.http.get<Product[]>(this.productUrl);  
    20.   }  
    21. }  
    Change the URL to suit your needs! It should point to your API URL.
  • Register the service in app/components/product/product.component.ts
    1. import { Component, OnInit } from '@angular/core';  
    2.   
    3. import { Product } from '../../models/Product';  
    4. import { ProductService } from '../../services/product.service';  
    5.   
    6. @Component({  
    7.   selector: 'app-product',  
    8.   templateUrl: './product.component.html',  
    9.   styleUrls: ['./product.component.css']  
    10. })  
    11.   
    12. export class ProductComponent implements OnInit {  
    13.   
    14.   products: Product[];  
    15.   
    16.   constructor(  
    17.     private productService: ProductService  
    18.   ) { }  
    19.   
    20.   ngOnInit() {  
    21.     this.getProducts();  
    22.   }  
    23.   
    24.   getProducts(): void {  
    25.     this.productService.getProducts().subscribe(result => this.products = result);  
    26.   }  
    27. }  
  • Change the component.html to display your products
    1. <table>  
    2.   <tr *ngFor="let product of products">  
    3.     <td>{{product.id}}</td>  
    4.     <td>{{product.name}}</td>  
    5.     <td>{{product.description}}</td>  
    6.   </tr>  
    7. </table>  
  • Register everything in app/app.module.ts
    1. import { BrowserModule } from '@angular/platform-browser';  
    2. import { NgModule } from '@angular/core';  
    3. import { AppComponent } from './app.component';  
    4. import { ProductComponent } from './components/product/product.component';  
    5. import { ProductService } from './services/product.service';  
    6. import { HttpClientModule } from '@angular/common/http';  
    7.   
    8. @NgModule({  
    9.   declarations: [  
    10.     AppComponent,  
    11.     ProductComponent,  
    12.   ],  
    13.   imports: [  
    14.     BrowserModule,  
    15.     HttpClientModule,  
    16.   ],  
    17.   providers: [  
    18.     ProductService,  
    19.   ],  
    20.   bootstrap: [AppComponent]  
    21. })  
    22. export class AppModule { }  
  • Add your component to the app/app.component.html to display it,
  1. <app-product></app-product>  
  • Run the API and go to its URL
  • Test Angular with ng serve -o
Where to go from here
Install Webpack in the Admin project: All your admin assets will be located in wwwroot. And you will have to manage them yourself in the bundleconfig.json file. If you want to work on the UI, it can become tiresome.
Make a config service for Angular to put all the routes to the API. I think it’s better to have them all in a single place.

No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...