TRUNGTQ

Think Big, Act Small, Fail Fast and Learn Rapidly

NAVIGATION - SEARCH

CRUD USING .NET CORE 1.0, ANGULARJS2, WEBAPI

In our previous article we have seen how to startup with .Net Core. In this article we will take a look on database operation according to previous sample application based previous concept.

If you are new to .Net Core, Please read previous post about .Net Core Startup

In this article we are going to explore,

  1. Create Database
  2. Use Entity Framework Core (Db First Approach),
    1. Overview EF Core
    2. Install Entity Framework
    3. Create Models
    4. Configure EF Service
  3. Use MVC 6
    1. Overview MVC6
    2. Use WebAPI
  4. Use AngularJS2
    1. Component,
    2. Route
    3. Service
  5. Configure Server
    1. Run App inside/outside IIS

Let’s get started with step by step:

Create Database Before we get started with IDE lets create a new database using SSMS2014. Name it as PhoneBook.

Create a table Named Contacts, copy & run the below script in SSMS2014

USE [PhoneBook]
GO

/****** Object:  Table [dbo].[Contacts]    Script Date: 8/7/2016 11:28:55 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Contacts](
	[ContactID] [int] IDENTITY(1,1) NOT NULL,
	[FirstName] [nvarchar](50) NULL,
	[LastName] [nvarchar](50) NULL,
	[Phone] [nvarchar](50) NULL,
	[Email] [nvarchar](50) NULL,
 CONSTRAINT [PK_Contacts] PRIMARY KEY CLUSTERED 
(
	[ContactID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
 

Let’s get started, following our previous topic I am going to use previous sample application, open it with Visual Studio 2015.

core_crud_1

It will automatically started restoring the dependencies. Build & run it. The application is working perfectly.

Install Entity Framework:Before install let’s have an overview on EF Core new Features:

  • Modelling: This includes Basic modelling, Data annotations, Relationships and much more.
  • Change Tracking:This includes Accessing tracked state, Snapshot, Notification change tracking.
  • SaveChanges: This includes Basic save functionality,Async SaveChanges,Transactions.
  • Query: This includes Basic LINQ support, Async query, Raw SQL queries
  • Database schema management: This includes Database creation/deletion APIs, Relational database migrations, and Reverse engineer from database.
  • Database providers: This includes EntityFramework.SqlServer, Sqlite, InMemory
  • Platforms: Supports Universal Windows Platform (UWP), .NET Core, Full .NET

Get more details about EF Core. Let’s add folders for Entity models in our sample app solution. core_crud_3

DbEntities: for model entities. The installation of EF is pretty much simple. Open project.json file, point tools section modify the section with below line.

"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0"

core_crud_4

Save changes after modification.

core_crud_5

Packages will automatically restored. Let’s get explanation what are those.

EntityFrameworkCore.SqlServer: Database Provider, that allows Entity Framework Core to be used with Microsoft SQL Server.

EntityFrameworkCore.Tools: Command line tool for EF Core. Includes Commands

For Package Manager Console:

  • Scaffold-DbContext,
  • Add-Migration,
  • Udate-Database

For Command Window:

  • dotnet ef dbcontext scaffold

We will see how to use both command. EntityFrameworkCore.SqlServer.Design: Design-time, that allows Entity Framework Core functionality (EF Core Migration) to be used with Microsoft SQL Server. To access the Command line tools we need to add EntityFrameworkCore.Tools intools section of our project.json.

"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"

core_crud_6

Save changes after modification. Command in Package Manager Console:Open package manager console.

core_crud_7

Input below command then hit enter,

Scaffold-DbContext "Server=DESKTOP-4T79RA1;Database=PhoneBook;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models/DbEntities

 core_crud_8core_crud_9

Command in Command Window:Open Command Window navigate to project directory, type

D:\Article\ASP-CORE\CRUD\CoreMVCAngular2\src\CoreMVCAngular>dotnet ef –help

Here a list of option will be shown in command window, we are going to use dbcontext in Commands.

core_crud_10

NextInput below command then hit enter,

D:\Article\ASP-CORE\CRUD\CoreMVCAngular2\src\CoreMVCAngular>dotnet ef dbcontext scaffold "Server=DESKTOP-4T79RA1;Database=PhoneBook;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models/CwEntities

core_crud_11

Here is a screen shot of both process that execute & generate models. We will keep DbEntities folder to work with & will delete the other folder.

core_crud_12

Configure EF Service: In PhoneBookContext Class add constructor

public PhoneBookContext(DbContextOptions<PhoneBookContext> options) :
       base(options)
{
}
 

In Startup class we need to enable EF services providing the connectionstring to

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();
  var connection = @"Server=DESKTOP-4T79RA1;Database=PhoneBook;Trusted_Connection=True;";
  services.AddDbContext<PhoneBookContext>(options => options.UseSqlServer(connection));
}
 

We have configure the EF services in our application, next we will work with MVC6 that is included in ASP.NET Core.

MVC 6:We have already discuss about MVC6 in our previous post, let’s have an overview on MVC6 new Features, once again:

  1. MVC+Web API+Web Pages = MVC6
  2. No System.Web
  3. Web pages & HTTP services is Unified
  4. Dependency injection built in
  5. Dynamic code compilation (Roslyn compiler)
  6. Open source &
  7. Support cross-platform build & run.
  8. Can be hosted in IIS or self-hosted(Outside IIS)

Ok, now let’s add a WebApi Controller to perform CRUD operation to database table. core_crud_13 In Solution Explorer add a new api folder, right click on it > Add New Item > Web API Controller Class > Add. Modify the initial template.

API Controller

[Route("api/[controller]")]
public class ContactController : Controller
{
    private PhoneBookContext _ctx = null;
    public ContactController(PhoneBookContext context)
    {
        _ctx = context;
    }
}
 

core_crud_14

You may notice that there is a new pattern [ ] in MVC6 attribute route, which is [RouteToken]. This mean that the route token is automatically take the controller name.

Like [Route("api/[controller]")] > [Route("api/Contact")]

Another thing is, we know Web API produces XML by default, now in MVC 6 we can set an attribute to change default produces to JSON type by putting attribute in Class label or on method label. In our case we have set it on method label.

[HttpGet("GetContact"), Produces("application/json")]

GET

// GET: api/Contact/GetContact
[HttpGet("GetContact"), Produces("application/json")]
public async Task<object> GetContact()
{
    List<Contacts> contacts = null;
    object result = null;
    try
    {
        using (_ctx)
        {
            contacts = await _ctx.Contacts.ToListAsync();
            result = new
            {
                contacts
            };
        }
    }
    catch (Exception ex)
    {
        ex.ToString();
    }
    return result;
}
 

POST

// POST api/Contact/PostContact
[HttpPost, Route("PostContact")]
public async Task<object> PostContact([FromBody]Contacts model)
{
    object result = null; int message = 0;
    if (model == null)
    {
        return BadRequest();
    }
    using (_ctx)
    {
        using (var _ctxTransaction = _ctx.Database.BeginTransaction())
        {
            try
            {
                _ctx.Contacts.Add(model);
                await _ctx.SaveChangesAsync();
                _ctxTransaction.Commit();
                message = (int)responseMessage.Success;
            }
            catch (Exception e)
            {
                _ctxTransaction.Rollback();
                e.ToString();
                message = (int)responseMessage.Error;
            }

            result = new
            {
                message
            };
        }
    }
    return result;
}
 

PUT

// PUT api/Contact/PutContact/5
[HttpPut, Route("PutContact/{id}")]
public async Task<object> PutContact(int id, [FromBody]Contacts model)
{
    object result = null; int message = 0;
    if (model == null)
    {
        return BadRequest();
    }
    using (_ctx)
    {
        using (var _ctxTransaction = _ctx.Database.BeginTransaction())
        {
            try
            {
                var entityUpdate = _ctx.Contacts.FirstOrDefault(x => x.ContactId == id);
                if (entityUpdate != null)
                {
                    entityUpdate.FirstName = model.FirstName;
                    entityUpdate.LastName = model.LastName;
                    entityUpdate.Phone = model.Phone;
                    entityUpdate.Email = model.Email;

                    await _ctx.SaveChangesAsync();
                }
                _ctxTransaction.Commit();
                message = (int)responseMessage.Success;
            }
            catch (Exception e)
            {
                _ctxTransaction.Rollback(); e.ToString();
                message = (int)responseMessage.Error;
            }

            result = new
            {
                message
            };
        }
    }
    return result;
}
 

DELETE

// DELETE api/Contact/DeleteContactByID/5
[HttpDelete, Route("DeleteContactByID/{id}")]
public async Task<object> DeleteContactByID(int id)
{
    object result = null; int message = 0;
    using (_ctx)
    {
        using (var _ctxTransaction = _ctx.Database.BeginTransaction())
        {
            try
            {
                var idToRemove = _ctx.Contacts.SingleOrDefault(x => x.ContactId == id);
                if (idToRemove != null)
                {
                    _ctx.Contacts.Remove(idToRemove);
                    await _ctx.SaveChangesAsync();
                }
                _ctxTransaction.Commit();
                message = (int)responseMessage.Success;
            }
            catch (Exception e)
            {
                _ctxTransaction.Rollback(); e.ToString();
                message = (int)responseMessage.Error;
            }

            result = new
            {
                message
            };
        }
    }
    return result;
}
 

So our Web API is ready to dealing with data to database, it’s time to work with client side scripting.  

AngularJS2: Our WebAPI is ready to deal with data from server. Now we are going to work in client-side code with typescript (.ts) files. First of all we need to create a master page to present our views in it. core_crud_15

Then we need to point this html file while app start, so let’s go to the startup.cs file to add below code snippet. This is the configuration for the default files Middleware.

Startup.cs

// app-specific root page(Index.html)
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("/Index.html");

need to add library

using Microsoft.AspNetCore.Builder;

Now add script library reference to the html page & define view point to load our app component views.

<spa-app>
   <p>
      <img src="img/ajax_small.gif" />  Please wait ...
   </p>
</spa-app>

Then we need to reference our bootstrap file in our page, that import & enable the our angular script to the page.

<script>
   System.config({ packages: { 'app': { defaultExtension: 'js' } }, });
   System.import('app/main').then(null, console.error.bind(console));
</script

Let’s put together all those in Index.html file.

Index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title></title>

    <base href="/">
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script src="../lib-npm/es6-shim/es6-shim.js"></script>
    <script src="../lib-npm/angular2/angular2-polyfills.js"></script>
    <script src="../lib-npm/systemjs/system.src.js"></script>
    <script src="../lib-npm/rxjs/Rx.js"></script>
    <script src="../lib-npm/angular2/angular2.js"></script>
    <script src="../lib-npm/angular2/router.js"></script>
    <script src="../lib-npm/angular2/http.js"></script>

    <link href="../lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="container">
        <spa-app>
            <p>
                <img src="img/ajax_small.gif" />  Please wait ...
            </p>
        </spa-app>

    </div>
    <script src="../lib/jquery/dist/jquery.min.js"></script>
    <script src="../lib/bootstrap/dist/js/bootstrap.min.js"></script>
    <script>
        System.config({ packages: { 'app': { defaultExtension: 'js' } }, });
        System.import('app/main').then(null, console.error.bind(console));
    </script>
</body>
</html>
 

  Bootstrap, Model, Component & Route Main.ts

/*This is the spa bootstrap File*/

//---------Import Angular2------------
import {bootstrap}    from 'angular2/platform/browser';
import {enableProdMode, provide} from 'angular2/core';

//---------Import External Components(Main Component)---------
import {MainComponent} from './app.component';

//---------Bootstrap Component---------
enableProdMode();
bootstrap(MainComponent);
App.component.ts

Hide   Shrink    Copy Code
/*Component Default view For SpaRoute */

//---------Import Angular2------------
import {Component, provide} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES, 
        ROUTER_PROVIDERS, LocationStrategy, 
        HashLocationStrategy, APP_BASE_HREF}                  from 'angular2/router';
 
//---------Import External Components---------
import {Home} from './home/home.component';
import {Contact} from './contact/contact.component';

//---------Declare Components---------
@Component({
    selector: 'spa-app',
    directives: [ROUTER_DIRECTIVES], //decorate link 
    templateUrl: 'app/main.view.html',
    providers: [
        ROUTER_PROVIDERS,
        //provide(APP_BASE_HREF, { useValue: '/' })
        provide(LocationStrategy, { useClass: HashLocationStrategy })
    ]
})

//---------Declare Route Config---------
@RouteConfig([
    { path: '/', name: 'Home', component: Home, useAsDefault: true },
    { path: '/Contact/...', name: 'Contact', component: Contact }
])


//---------Export This Component Class---------
export class MainComponent {
    title: string;
    constructor() {
        this.title = 'Welcome to [.NetCore+MVC6+Angular2] SPA';
    }
}


  Home.ts

import {Component} from 'angular2/core';

@Component({
    selector: 'home',
    templateUrl: `app/home/home.view.html`
})
export class Home {
    constructor() {   
    }
}
Contact.model.ts

Hide   Copy Code
export class ContactModel {
    contactId: number;
    firstName: string;
    lastName: string;
    phone: string;
    email: string;
}


Contact.component.ts

//---------Import Angular2------------
import {Component}                                            from 'angular2/core';
import {ROUTER_DIRECTIVES, RouteConfig}                       from 'angular2/router';

//---------Import External Components---------
import {ContactMain}                                          from './contact.main';

//---------Declare Components---------
@Component({
    selector: 'contacts',
    template: `<router-outlet></router-outlet>`,
    directives: [ROUTER_DIRECTIVES]
})
@RouteConfig([
    { path: '/', name: 'ManageContact', component: ContactMain, useAsDefault: true },
])
export class Contact {
    constructor() { }
}
 

  Contact.main.ts

//---------Import Angular2------------
import {Component, OnInit}                                     from 'angular2/core';
import {HTTP_PROVIDERS, Http}                                  from 'angular2/http';
import {ROUTER_DIRECTIVES, RouteConfig}                        from 'angular2/router';
import {FORM_DIRECTIVES,
    FormBuilder, Control, ControlGroup, Validators}            from 'angular2/common';

//---------Import External Components---------
import {ContactModel}                                          from './contact.model';
import {ContactService}                                        from './contact.service';
import {customvalidators}                                      from './customvalidators';

//---------Declare Components---------
@Component({
    selector: 'contact-list',
    templateUrl: `app/contact/contact.view.html`,
    directives: [ROUTER_DIRECTIVES, FORM_DIRECTIVES],
    providers: [ContactService, HTTP_PROVIDERS]
})

//---------Export This Component Class---------
export class ContactMain implements OnInit {

    public resmessage: string;
    public addmessage: string;
    public listmessage: string;
    public contact: ContactModel;
    public contacts: ContactModel[];
    public editContactId: any

    //Form Control
    contactForm: ControlGroup;
    firstName: Control;
    email: Control;
    phone: Control;

    //Constructor
    constructor(private builder: FormBuilder,
        private contactService: ContactService) {
        this.addmessage = 'Add New Contact';
        this.listmessage = 'All Contact';
        this._formGroup();
    }

    ngOnInit() {
        this.resmessage = "";
        this.editContactId = 0;
        this.getContacts();
    }

    //Form Group
    _formGroup() {
        this.firstName = new Control('', Validators.required);
        this.email = new Control('', Validators.compose([Validators.required, customvalidators.emailValidator]));
        this.phone = new Control('');

        this.contactForm = this.builder.group({
            firstName: this.firstName,
            email: this.email,
            phone: this.phone
        });
    }

    //Get All 
    getContacts() {
        //debugger
        this.contactService.getContacts().subscribe(
            contacts => this.contacts = contacts
        );
    }

    //Save Form
    saveContact(contact) {
        //debugger
        this.contactService.saveContact(contact)
            .subscribe(response => {
                this.resmessage = response;
                this.getContacts();
                this.reset();
            });
    }

    //Get by ID
    editContact(e, m) {
        //debugger
        e.preventDefault();
        this.editContactId = m.contactId;
        this.contactService.getContactByID(m.contactId)
            .subscribe(response => {
                this.contact = response;
                this.firstName.updateValue(this.contact.firstName);
                this.email.updateValue(this.contact.email);
                this.phone.updateValue(this.contact.phone);
            });
    }

    //Save Form
    updateContact(contact: any) {
        //debugger
        if (this.editContactId > 0) {
            this.contactService.updateContact(contact, this.editContactId)
                .subscribe(response => {
                    this.resmessage = response;
                    this.getContacts();
                    this.reset();
                });
        }
    }

    //Delete
    deleteContact(e, m) {
        //debugger
        e.preventDefault();
        var IsConf = confirm('You are about to delete ' + m.firstName + '. Are you sure?');
        if (IsConf) {
            this.contactService.deleteContact(m.contactId)
                .subscribe(response => {
                    this.resmessage = response;
                    this.getContacts();
                });
        }
    }

    reset() {
        this.editContactId = 0;
        this._formGroup();
    }
}
 

Let’s take a closer look at below code snippet, we have the service method call in hare, but the unknown term Subscribe -What is it for? Below we have a simple explanation.

this.contactService.getContacts().subscribe(
            contacts => this.contacts = contacts
        );

Subscribe:The subscriber function to be passed to the Observable constructor.  

Services In our service file we have Http service [Get, GetByID, Post, Put, Delete] that connect with WebAPI to perform operation Create, Read, Update & Delete. GET ALL:Performs a request with `get` http method.For Collection of Object

/Get
    getContacts(): Observable<ContactModel[]> {
        //debugger
        return this._http.get(this._getUrl)
            .map(res => <ContactModel[]>res.json())
            .catch(this.handleError);
    }

GET By ID:Performs a request with `get` http method.For Single Object

//GetByID
    getContactByID(id: string): Observable<ContactModel> {
        //debugger
        var getByIdUrl = this._getByIdUrl + '/' + id;
        return this._http.get(getByIdUrl)
            .map(res => <ContactModel>res.json())
            .catch(this.handleError);
    }

POST:Performs a request with `post` http method.

//Post
    saveContact(contact: ContactModel): Observable<string> {
        //debugger
        let body = JSON.stringify(contact);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        //http.post(url: string, body: string, options ?: RequestOptionsArgs): Observable<Response>
        return this._http.post(this._saveUrl, body, options)
            .map(res => res.json().message)
            .catch(this.handleError);
    }

PUT:Performs a request with `put` http method.

//Put
    updateContact(contact: ContactModel, id: string): Observable<string> {
        //debugger
        var updateUrl = this._updateUrl + '/' + id;
        var body = JSON.stringify(contact);
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');

        //http.post(url: string, body: string, options ?: RequestOptionsArgs): Observable<Response>
        return this._http.put(updateUrl, body, { headers: headers })
            .map(response => response.json().message)
            .catch(this.handleError);
    }

DELETE:Performs a request with `delete` http method.

//Delete
    deleteContact(id: string): Observable<string> {
        //debugger
        var deleteByIdUrl = this._deleteByIdUrl + '/' + id

        //http.post(url: string, options ?: RequestOptionsArgs): Observable<Response>
        return this._http.delete(deleteByIdUrl)
            .map(response => response.json().message)
            .catch(this.handleError);
    }

Observable : [Observable<T>] A representation of any set of values over any amount of time. This the most basic building block of RxJS. Let’s put it together in Contact.service file.

Contact.service.ts

import {Injectable, Component}                            from 'angular2/core';
import {Http, Request, RequestMethod, Response,
    RequestOptions, Headers}                              from 'angular2/http';
import 'rxjs/Rx';
import {Observable}                                       from 'rxjs/Observable';
import {ContactModel}                                     from './contact.model';

@Component({
    providers: [Http]
})

@Injectable()
export class ContactService {
    public headers: Headers;
    constructor(private _http: Http) {
    }

    public _saveUrl: string = '/api/Contact/PostContact/';
    public _updateUrl: string = '/api/Contact/PutContact/';
    public _getUrl: string = '/api/Contact/GetContact/';
    public _getByIdUrl: string = '/api/Contact/GetContactByID/';
    public _deleteByIdUrl: string = '/api/Contact/DeleteContactByID/';

    //Get
    getContacts(): Observable<ContactModel[]> {
        //debugger
        return this._http.get(this._getUrl)
            .map(res => <ContactModel[]>res.json())
            .catch(this.handleError);
    }

    //GetByID
    getContactByID(id: string): Observable<ContactModel> {
        //debugger
        var getByIdUrl = this._getByIdUrl + '/' + id;
        return this._http.get(getByIdUrl)
            .map(res => <ContactModel>res.json())
            .catch(this.handleError);
    }

    //Post
    saveContact(contact: ContactModel): Observable<string> {
        //debugger
        let body = JSON.stringify(contact);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        //http.post(url: string, body: string, options ?: RequestOptionsArgs): Observable<Response>
        return this._http.post(this._saveUrl, body, options)
            .map(res => res.json().message)
            .catch(this.handleError);
    }

    //Put
    updateContact(contact: ContactModel, id: string): Observable<string> {
        //debugger
        var updateUrl = this._updateUrl + '/' + id;
        var body = JSON.stringify(contact);
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');

        //http.post(url: string, body: string, options ?: RequestOptionsArgs): Observable<Response>
        return this._http.put(updateUrl, body, { headers: headers })
            .map(response => response.json().message)
            .catch(this.handleError);
    }

    //Delete
    deleteContact(id: string): Observable<string> {
        //debugger
        var deleteByIdUrl = this._deleteByIdUrl + '/' + id

        //http.post(url: string, options ?: RequestOptionsArgs): Observable<Response>
        return this._http.delete(deleteByIdUrl)
            .map(response => response.json().message)
            .catch(this.handleError);
    }

    private handleError(error: Response) {
        return Observable.throw(error.json().error || 'Opps!! Server error');
    }
}

Let’s discus about form in angular2, there are two strategy of angular2 form

  1. Template-driven
  2. Model-driven

 Template-driven In template-driven form directive are added declaratively in the template.

<input id="firstName" type="text"
       class="form-control"
       placeholder="FirstName" [ngFormControl]="firstName" required>

Noticed that the validator is added declaratively with the input element “required”.  

Model-driven In our sample app we have used model-driven form that has ngFormModel & ngFormControl. Here ngFormControl is bind with input element to get the input values through the control.

ngFormModel:binding it to a controller variable “contactForm”

<form [ngFormModel]="contactForm">

ngFormControl

<input id="firstName" type="text" 
                       class="form-control" 
                       placeholder="FirstName" [ngFormControl]="firstName">

ControlGroup is contain of several Controls.

//Form Control
contactForm: ControlGroup;
firstName: Control;
email: Control;
phone: Control;

An Injected FormBulder use the builder to create the control group which is pass as key value pairs.

private builder: FormBuilder

//Form Group
_formGroup() {
    //Set Initial Values to the Control & Validators
    this.firstName = new Control('', Validators.required);
    this.email = new Control('', Validators.compose([Validators.required, customvalidators.emailValidator]));
    this.phone = new Control('');

    //Pass the grouped controls as key value pairs
    this.contactForm = this.builder.group({
        firstName: this.firstName,
        email: this.email,
        phone: this.phone
    });
}

The validations are also checked in our component. Below we have our Model-driven complete form. Form

<form [ngFormModel]="contactForm">
            <div class="form-group" [ngClass]="{ 'has-error' : !firstName.valid }">
                <label class="control-label" for="firstName">Username</label>
                <em *ngIf="!firstName.valid">*</em>
                <input id="firstName" type="text" 
                       class="form-control" 
                       placeholder="FirstName" [ngFormControl]="firstName">
            </div>

            <div class="form-group" [ngClass]="{ 'has-error' : !email.valid }">
                <label class="control-label" for="email">Email</label>
                <em *ngIf="!email.valid">*</em>
                <input id="email" type="email" 
                       class="form-control" 
                       placeholder="Email" [ngFormControl]="email">
            </div>

            <div class="form-group">
                <label class="control-label" for="phone">Phone</label>
                <input id="phone" type="text" class="form-control" placeholder="Phone" [ngFormControl]="phone">
            </div>

            <div class="form-group">
                <button type="submit" class="btn btn-danger" (click)="reset()">Reset</button>
                <button type="submit" class="btn btn-primary" (click)="saveContact(contactForm.value)" 
                        *ngIf="editContactId == 0" 
                        [disabled]="!contactForm.valid">Create</button>
                <button type="submit" class="btn btn-success" (click)="updateContact(contactForm.value)" 
                        *ngIf="editContactId > 0" 
                        [disabled]="!contactForm.valid">Update</button>
            </div>
</form>
 

  Here is the complete contact view page which we have used in our application.

Contact.view.html

<div class="row">

    <div class="col-sm-4">
        <h3>Phone Book {{addmessage}}</h3>
        <form [ngFormModel]="contactForm">
            <div class="form-group" [ngClass]="{ 'has-error' : !firstName.valid }">
                <label class="control-label" for="firstName">Username</label>
                <em *ngIf="!firstName.valid">*</em>
                <input id="firstName" type="text" 
                       class="form-control" 
                       placeholder="FirstName" [ngFormControl]="firstName">
            </div>

            <div class="form-group" [ngClass]="{ 'has-error' : !email.valid }">
                <label class="control-label" for="email">Email</label>
                <em *ngIf="!email.valid">*</em>
                <input id="email" type="email" 
                       class="form-control" 
                       placeholder="Email" [ngFormControl]="email">
            </div>

            <div class="form-group">
                <label class="control-label" for="phone">Phone</label>
                <input id="phone" type="text" class="form-control" placeholder="Phone" [ngFormControl]="phone">
            </div>

            <div class="form-group">
                <button type="submit" class="btn btn-danger" (click)="reset()">Reset</button>
                <button type="submit" class="btn btn-primary" (click)="saveContact(contactForm.value)" 
                        *ngIf="editContactId == 0" 
                        [disabled]="!contactForm.valid">Create</button>
                <button type="submit" class="btn btn-success" (click)="updateContact(contactForm.value)" 
                        *ngIf="editContactId > 0" 
                        [disabled]="!contactForm.valid">Update</button>
            </div>
        </form>
        <span class="warning">{{resmessage}}</span>
    </div>
    <div class="col-sm-8">
        <h3>Phone Book {{listmessage}}</h3>
        <table style="width:100%" class="table table-striped">
            <tr>
                <th>ID</th>
                <th>Firstname</th>
                <th>Email</th>
                <th>Phone</th>
                <th>Option</th>
            </tr>
            <tr *ngFor="#contact of contacts">
                <td>{{ contact.contactId }}</td>
                <td>{{ contact.firstName }}</td>
                <td>{{ contact.email }}</td>
                <td>{{ contact.phone }}</td>
                <td>
                    <a href="javascript:void(0)"
                       (click)="deleteContact($event, contact)"
                       class="btn btn-danger btn-xs pull-right">Delete</a>
                    <a href="javascript:void(0)"
                       (click)="editContact($event, contact)"
                       class="btn btn-primary btn-xs pull-right">Edit</a>
                </td>
            </tr>
        </table>
    </div>
</div>
 

  This is all about from our angular section in our sample app, that we have used to perform client-end operation, now it’s time to build & run the application. Next we will get overview on server configuration.  

ConfigureServer: Outside IIS (Weblistener):We can host & run our application without an IIS environment, we need to add command object that tell the hosting to use the HTTP server weblistener (Windows-only).

"commands": {
    "OutsideIIS": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Weblistener --server.urls http://localhost:5001"
  },

Make sure the dependencies is exist

"Microsoft.AspNetCore.Server.WebListener": "0.1.0"

Now go to solution explorer right click project > properties > Debug , change the profile to OutsideIIS. core_crud_16 Set launch URL, then save & run the application. core_crud_17 Server will start with hosting environment details

core_crud_18

The application is running on url http://localhost:5000 with details request info.

core_crud_19

Inside IIS (Kestrel):Kestrel is a cross-platform web server which is run behind IIS or Nginx.

core_crud_20

Change to IIS Express from navigation bar dropdown list to run the application using IIS. Ok let’s run our application to see the way how it’s work.

Output: Here we can see the application output with welcome message & the data is listed from the server through our MVC6 –WebAPI. Here we can perform CRUD operation with required validation.  

core_crud_21

Hope this will help :)

LINK: https://www.codeproject.com/Articles/1118189/CRUD-USING-NET-CORE-ANGULARJS-WEBAPI

 

Using ASP.NET Core, Entity Framework Core and ASP.NET Boilerplate to Create NLayered Web Application (Part I)

A step by step guide to create a web application based on ASP.NET Core, Entity Framework Core and ASP.NET Boilerplate frameworks with automated tests.

Contents

  • Introduction
    • Prerequirements
  • Create the Application
  • Developing the Application
    • Creating a Task Entity
    • Adding Task to DbContext
    • Creating the First Database Migration
    • Creating the Database
    • Task Application Service
    • Testing the TaskAppService
    • Task List View
      • Adding a New Menu Item
      • Creating the TaskController and ViewModel
      • Task List Page
      • Localization
      • Filtering Tasks
    • Automated Testing Task List Page
  • More
  • Article History

Introduction

This is first part of the "Using ASP.NET Core, Entity Framework Core and ASP.NET Boilerplate to Create NLayered Web Application" article series. See other parts:

In this article, I'll show to create a simple layered web application using the following tools:

We will also use Log4Net and AutoMapper which are included in ABP startup template by default. We will use the following techniques:

The project will be developed here is a simple task management application where tasks can be assigned to people. Instead of developing the application layer by layer, I will go to vertical and change the layers as the application grows. While the application grows, I will introduce some features of ABP and other frameworks as needed.

Prerequirements

Following tools should be installed in your machine to be able to run/develop the sample application:

Create the Application

I used ABP's startup template (http://www.aspnetboilerplate.com/Templates) to create a new web application named "Acme.SimpleTaskApp" (company name, "Acme" here, is optional while creating templates):

ABP startup template creation

It creates a layered solution as shown below: 

Startup template projects

It includes 6 projects starting with the name that I entered as the project name:

  • .Core project is for domain/business layer (entities, domain services...)
  • .Application project is for application layer (DTOs, application services...)
  • .EntityFramework project is for EF Core integration (abstracts EF Core from other layers).
  • .Web project is for ASP.NET MVC layer.
  • .Tests project is for unit and integration tests (up to application layer, excluding web layer)
  • .Web.Tests project is for ASP.NET Core integrated tests (complete integration test for server side).

When you run the application, you can see the user interface of the template:

Template Home Page

It contains a top menu, empty Home and About pages and a language switch dropdown.

Developing the Application

Creating a Task Entity

I want to start with a simple Task entity. Since an entity is part of the domain layer, I added it into the .Coreproject:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using Abp.Timing;

namespace Acme.SimpleTaskApp.Tasks
{
    [Table("AppTasks")]
    public class Task : Entity, IHasCreationTime
    {
        public const int MaxTitleLength = 256;
        public const int MaxDescriptionLength = 64 * 1024; //64KB

        [Required]
        [MaxLength(MaxTitleLength)]
        public string Title { get; set; }

        [MaxLength(MaxDescriptionLength)]
        public string Description { get; set; }

        public DateTime CreationTime { get; set; }

        public TaskState State { get; set; }

        public Task()
        {
            CreationTime = Clock.Now;
            State = TaskState.Open;
        }

        public Task(string title, string description = null)
            : this()
        {
            Title = title;
            Description = description;
        }
    }

    public enum TaskState : byte
    {
        Open = 0,
        Completed = 1
    }
}


  • I derived from ABP's base Entity class, which includes Id property as int by default. We can use the generic version, Entity<TPrimaryKey>, to choice a different PK type.
  • IHasCreationTime is a simple interface just defines CreationTime property (it's good to use a standard name for CreationTime).
  • Task entity defines a required Title and an optional Description.
  • TaskState is a simple enum to define states of a Task.
  • Clock.Now returns DateTime.Now by default. But it provides an abstraction, so we can easily switch to DateTime.UtcNow in the feature if it's needed. Always use Clock.Now instead of DateTime.Now while working with ABP framework.
  • I wanted to store Task entities into AppTasks table in the database.

Adding Task to DbContext

.EntityFrameworkCore project includes a pre-defined DbContext. I should add a DbSet for the Task entity into the DbContext:

public class SimpleTaskAppDbContext : AbpDbContext
{
    public DbSet<Task> Tasks { get; set; }

    public SimpleTaskAppDbContext(DbContextOptions<SimpleTaskAppDbContext> options) 
        : base(options)
    {

    }
}


Now, EF Core knows that we have a Task entity. 

Creating the First Database Migration 

We will create an initial migration to create database and the AppTasks table. I open the Windows Command Line and run the following command (active directory should be the root directory of .EntityFrameworkCore project):

EF Core Initial Migration

This command creates a Migrations folder in the .EntityFrameworkCore project which includes a migration class and a snapshot of our database model:

EntityFrameworkCore Project Migrations 

Automatically generated "Initial" migration class is shown below:

public partial class Initial : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "AppTasks",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                CreationTime = table.Column<DateTime>(nullable: false),
                Description = table.Column<string>(maxLength: 65536, nullable: true),
                State = table.Column<byte>(nullable: false),
                Title = table.Column<string>(maxLength: 256, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_AppTasks", x => x.Id);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "AppTasks");
    }
}


This code is used to create AppTasks table when we execute the migrations to the database (see entity framework documentation for more information on migrations).

Creating the Database

To create the database, I run the following command in the command line:

dotnet ef database update

This command created a database named SimpleTaskAppDb in the local SQL Server and executed migrations (currently, there is a single, "Initial", migration):

Created Database

Now, I have a Task entity and corresponding table in the database.  I entered a few sample Tasks to the table:

AppTasks table

Note that, the database connection string is defined in appsettings.json in the .Web application.

Task Application Service

Application Services are used to expose domain logic to the presentation layer. An Application Service is called from presentation layer with a Data Transfer Object (DTO) as parameter (if needed), uses domain objects to perform some specific business logic and returns a DTO back to the presentation layer (if needed).

I'm creating the first application service, TaskAppService, into the .Application project to perform task related application logic. First, I wanted to define an interface for the app service:

public interface ITaskAppService : IApplicationService
{
    Task<ListResultOutput<TaskListDto>> GetAll(GetAllTasksInput input);
}

Defining an interface is not required, but suggested. By convention, all app services should implement IApplicationService interface in ABP. I just created a GetAll method to query tasks. To do that, I also defined the following DTOs:

public class GetAllTasksInput
{
    public TaskState? State { get; set; }
}

[AutoMapFrom(typeof(Task))] public class TaskListDto : EntityDto, IHasCreationTime { public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } }
  • GetAllTasksInput DTO defines input parameters of the GetAll app service method. Instead of directly defining the state as method parameter, I added it into a DTO object. Thus, I can add other parameters into this DTO later without breaking my existing clients (we could directly add a state parameter to the method).
  • TaskListDto is used to return a Task data. It's derived from EntityDto, which just defines an Idproperty (we could add Id to our Dto and not derive from EntityDto). We defined [AutoMapFrom] attribute to create AutoMapper mapping from Task entity to TaskListDto. This attribute is defined in Abp.AutoMapper nuget package.
  • Lastly, ListResultOutput is a simple class contains a list of items (we could directly return a List<TaskListDto>).

Now, we can implement the ITaskAppService as shown below:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Domain.Repositories;
using Abp.Linq.Extensions;
using Acme.SimpleTaskApp.Tasks.Dtos;
using Microsoft.EntityFrameworkCore;

namespace Acme.SimpleTaskApp.Tasks
{
    public class TaskAppService : SimpleTaskAppAppServiceBase, ITaskAppService
    {
        private readonly IRepository<Task> _taskRepository;

        public TaskAppService(IRepository<Task> taskRepository)
        {
            _taskRepository = taskRepository;
        }

        public async Task<ListResultOutput<TaskListDto>> GetAll(GetAllTasksInput input)
        {
            var tasks = await _taskRepository
                .GetAll()
                .WhereIf(input.State.HasValue, t => t.State == input.State.Value)
                .OrderByDescending(t => t.CreationTime)
                .ToListAsync();

            return new ListResultOutput<TaskListDto>(
                ObjectMapper.Map<List<TaskListDto>>(tasks)
            );
        }
    }
}


  • TaskAppService is derived from SimpleTaskAppAppServiceBase included in the startup template (which is derived from ABP's ApplicationService class). This is not required, app services can be plain classes. But ApplicationService base class has some pre-injected services (like ObjectMapper used here).
  • I used dependency injection to get a repository.
  • Repositories are used to abstract database operations for entities. ABP creates a pre-defined repository (like IRepository<Task> here) for each entity to perform common tasks. IRepository.GetAll()used here returns an IQueryable to query entities.
  • WhereIf is an extension method of ABP to simplify conditional usage of IQueryable.Where method.
  • ObjectMapper (which somes from the ApplicationService base class and implemented via AutoMapper by default) is used to map list of Task objects to list of TaskListDtos objects.

Testing the TaskAppService

Before going further to create user interface, I want to test TaskAppService. You can skip this section if you don't interest in automated testing.

Startup template contains .Tests project to test our code. It uses EF Core's InMemory database provider instead of SQL Server. Thus, our unit tests can work without a real database. It creates a separated database for each test. Thus, tests are isolated from each other. We can use TestDataBuilder class to add some initial test data to InMemory database before running tests. I changed TestDataBuilder as shown below:

public class TestDataBuilder
{
    private readonly SimpleTaskAppDbContext _context;

    public TestDataBuilder(SimpleTaskAppDbContext context)
    {
        _context = context;
    }

    public void Build()
    {
        _context.Tasks.AddRange(
            new Task("Follow the white rabbit", "Follow the white rabbit in order to know the reality."),
            new Task("Clean your room") { State = TaskState.Completed }
            );
    }
}


You can see the sample project's source code to understand where and how TestDataBuilder is used. I added two tasks (one of them is completed) to the dbcontext. So, I can write my tests assuming that there are two Tasks in the database. My first integration test tests the TaskAppService.GetAll method we created above.

public class TaskAppService_Tests : SimpleTaskAppTestBase
{
    private readonly ITaskAppService _taskAppService;

    public TaskAppService_Tests()
    {
        _taskAppService = Resolve<ITaskAppService>();
    }

    [Fact]
    public async System.Threading.Tasks.Task Should_Get_All_Tasks()
    {
        //Act
        var output = await _taskAppService.GetAll(new GetAllTasksInput());

        //Assert
        output.Items.Count.ShouldBe(2);
    }

    [Fact]
    public async System.Threading.Tasks.Task Should_Get_Filtered_Tasks()
    {
        //Act
        var output = await _taskAppService.GetAll(new GetAllTasksInput { State = TaskState.Open });

        //Assert
        output.Items.ShouldAllBe(t => t.State == TaskState.Open);
    }
}


I created two different tests to test GetAll method as shown above. Now, I can open Test Explorer (from Test\Windows\Test Explorer in the main menu of VS) and run the unit tests:

Test explorer

All of them succeed. The last one was a pre-built test in the startup template, which we can ignore for now.

Notice that; ABP startup template comes with xUnit and Shouldly installed by default. So, we used them to write our tests.

Task List View

Now, I know that TaskAppService is properly working. I can start to create a page to list all tasks.

Adding a New Menu Item

 First, I'm adding a new item to the top menu:

public class SimpleTaskAppNavigationProvider : NavigationProvider
{
    public override void SetNavigation(INavigationProviderContext context)
    {
        context.Manager.MainMenu
            .AddItem(
                new MenuItemDefinition(
                    "Home",
                    L("HomePage"),
                    url: "",
                    icon: "fa fa-home"
                    )
            ).AddItem(
                new MenuItemDefinition(
                    "About",
                    L("About"),
                    url: "Home/About",
                    icon: "fa fa-info"
                    )
            ).AddItem(
                new MenuItemDefinition(
                    "TaskList",
                    L("TaskList"),
                    url: "Tasks",
                    icon: "fa fa-tasks"
                    )
            );
    }

    private static ILocalizableString L(string name)
    {
        return new LocalizableString(name, SimpleTaskAppConsts.LocalizationSourceName);
    }
}


Startup template comes with two pages: Home and About, as shown above. We can change them or create new pages. I prefered to leave them for now and create a new menu item.

Creating the TaskController and ViewModel

I'm creating a new controller class, TasksController, in the .Web project as shown below:

public class TasksController : SimpleTaskAppControllerBase
{
    private readonly ITaskAppService _taskAppService;

    public TasksController(ITaskAppService taskAppService)
    {
        _taskAppService = taskAppService;
    }

    public async Task<ActionResult> Index(GetAllTasksInput input)
    {
        var output = await _taskAppService.GetAll(input);
        var model = new IndexViewModel(output.Items);
        return View(model);
    }
}


  • I derived from SimpleTaskAppControllerBase (which is derived from AbpController) that contains common base code for Controllers in this application.
  • I injected ITaskAppService in order to get list of tasks.
  • Instead of directly passing result of the GetAll method to the view, I created an IndexViewModel class in the .Web project which is shown below:
public class IndexViewModel
{
    public IReadOnlyList<TaskListDto> Tasks { get; }

    public IndexViewModel(IReadOnlyList<TaskListDto> tasks)
    {
        Tasks = tasks;
    }

    public string GetTaskLabel(TaskListDto task)
    {
        switch (task.State)
        {
            case TaskState.Open:
                return "label-success";
            default:
                return "label-default";
        }
    }
}


This simple view model gets a list of tasks (which is provided from ITaskAppService) in it's constructor. It also has GetTaskLabel method that will be used in the view to select a Bootstrap label class for given task.

Task List Page

And finally, the Index view is shown below:

@model Acme.SimpleTaskApp.Web.Models.Tasks.IndexViewModel

@{
    ViewBag.Title = L("TaskList");
    ViewBag.ActiveMenu = "TaskList"; //Matches with the menu name in SimpleTaskAppNavigationProvider to highlight the menu item
}

<h2>@L("TaskList")</h2>

<div class="row">
    <div>
        <ul class="list-group">
            @foreach (var task in Model.Tasks)
            {
                <li class="list-group-item">
                    <span class="pull-right label @Model.GetTaskLabel(task)">@L($"TaskState_{task.State}")</span>
                    <h4 class="list-group-item-heading">@task.Title</h4>
                    <div class="list-group-item-text">
                        @task.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")
                    </div>
                </li>
            }
        </ul>
    </div>
</div>


We simply used given model to render the view using Bootstrap's list group component. Here, we used IndexViewModel.GetTaskLabel() method to get label types for tasks. Rendered page will be like that:

Task list

Localization

We used L method in the view which comes from ABP framework. It's used to localize strings. We have define localized strings in Localization/Source folder in the .Core project as .json files. English localization is shown below:

{
  "culture": "en",
  "texts": {
    "HelloWorld": "Hello World!",
    "ChangeLanguage": "Change language",
    "HomePage": "HomePage",
    "About": "About",
    "Home_Description": "Welcome to SimpleTaskApp...",
    "About_Description": "This is a simple startup template to use ASP.NET Core with ABP framework.",
    "TaskList": "Task List",
    "TaskState_Open": "Open",
    "TaskState_Completed": "Completed"
  }
}


Most of the texts are coming from startup template and can be deleted. I just added the last 3 lines and used in the view above. While using ABP's localization is pretty simple, you can see localization document for more information on the localization system.

Filtering Tasks

As shown above, TaskController actually gets a GetAllTasksInput that can be used to filter tasks. So, we can add a dropdown to task list view to filter tasks. First, I added the dropdown to the view (I added inside the header):

<h2>
    @L("TaskList")
    <span class="pull-right">
        @Html.DropDownListFor(
           model => model.SelectedTaskState,
           Model.GetTasksStateSelectListItems(LocalizationManager),
           new
           {
               @class = "form-control",
               id = "TaskStateCombobox"
           })
    </span>
</h2>


Then I changed IndexViewModel to add SelectedTaskState property and GetTasksStateSelectListItems method:

public class IndexViewModel
{
    //...

    public TaskState? SelectedTaskState { get; set; }

    public List<SelectListItem> GetTasksStateSelectListItems(ILocalizationManager localizationManager)
    {
        var list = new List<SelectListItem>
        {
            new SelectListItem
            {
                Text = localizationManager.GetString(SimpleTaskAppConsts.LocalizationSourceName, "AllTasks"),
                Value = "",
                Selected = SelectedTaskState == null
            }
        };

        list.AddRange(Enum.GetValues(typeof(TaskState))
                .Cast<TaskState>()
                .Select(state =>
                    new SelectListItem
                    {
                        Text = localizationManager.GetString(SimpleTaskAppConsts.LocalizationSourceName, $"TaskState_{state}"),
                        Value = state.ToString(),
                        Selected = state == SelectedTaskState
                    })
        );

        return list;
    }
}


We should set SelectedTaskState in the controller:

public async Task<ActionResult> Index(GetAllTasksInput input)
{
    var output = await _taskAppService.GetAll(input);
    var model = new IndexViewModel(output.Items)
    {
        SelectedTaskState = input.State
    };
    return View(model);
}


Now, we can run the application to see the combobox at the top right of the view:

Task list

I added the combobox but it can not work yet. I'll write a simple javascript code to re-request/refresh task list page when combobox value changes. So, I'm creating wwwroot\js\views\tasks\index.js file in the .Web project:

(function ($) {
    $(function () {

        var _$taskStateCombobox = $('#TaskStateCombobox');

        _$taskStateCombobox.change(function() {
            location.href = '/Tasks?state=' + _$taskStateCombobox.val();
        });

    });
})(jQuery);


Before including this javascript file into my view, I used Bundler & Minifier VS extension (which is default way of minifying files in ASP.NET Core projects) to minify the script:

Minify js

This adds the following lines into bundleconfig.json file in the .Web project:

{
  "outputFileName": "wwwroot/js/views/tasks/index.min.js",
  "inputFiles": [
    "wwwroot/js/views/tasks/index.js"
  ]
}


And creates a minified version of the script:

Minified js file

Whenever I change the index.js, index.min.js is automatically re-generated. Now, I can include the javascript file into my page:

@section scripts
{
    <environment names="Development">
        <script src="~/js/views/tasks/index.js"></script>
    </environment>

    <environment names="Staging,Production">
        <script src="~/js/views/tasks/index.min.js"></script>
    </environment>
}


With this code, our view will use index.js in development and index.min.js (minified version) in production. This is a common approach in ASP.NET Core MVC projects.

Automated Testing Task List Page

We can create integration tests that is also integrated to ASP.NET Core MVC infrastructure. Thus, we can completely test our server side code. You can skip this section if you don't interest in automated testing.

ABP startup template includes a .Web.Tests project to do that. I created a simple test to request to TaskController.Index and check the response:

public class TasksController_Tests : SimpleTaskAppWebTestBase
{
    [Fact]
    public async System.Threading.Tasks.Task Should_Get_Tasks_By_State()
    {
        //Act

        var response = await GetResponseAsStringAsync(
            GetUrl<TasksController>(nameof(TasksController.Index), new
                {
                    state = TaskState.Open
                }
            )
        );

        //Assert

        response.ShouldNotBeNullOrWhiteSpace();
    }
}


GetResponseAsStringAsync and GetUrl methods are some helper methods provided by AbpAspNetCoreIntegratedTestBase class of ABP. We can instead directly use the Client (an instance of HttpClient) property to make requests. But using these shortcut methods makes it easier. See integration testing documentation of ASP.NET Core for more.

When I debug the test, I can see the response HTML:

Web test

That shows the Index page returned a response without any exception. But... we may want to go more and check if returned HTML is what we expect. There are some libraries can be used to parse HTML. AngleSharp is one of them and comes as pre-installed in ABP startup template's .Web.Tests project. So, I used it to check the created HTML code:

public class TasksController_Tests : SimpleTaskAppWebTestBase
{
    [Fact]
    public async System.Threading.Tasks.Task Should_Get_Tasks_By_State()
    {
        //Act

        var response = await GetResponseAsStringAsync(
            GetUrl<TasksController>(nameof(TasksController.Index), new
                {
                    state = TaskState.Open
                }
            )
        );

        //Assert

        response.ShouldNotBeNullOrWhiteSpace();

        //Get tasks from database
        var tasksInDatabase = await UsingDbContextAsync(async dbContext =>
        {
            return await dbContext.Tasks
                .Where(t => t.State == TaskState.Open)
                .ToListAsync();
        });

        //Parse HTML response to check if tasks in the database are returned
        var document = new HtmlParser().Parse(response);
        var listItems = document.QuerySelectorAll("#TaskList li");
            
        //Check task count
        listItems.Length.ShouldBe(tasksInDatabase.Count);

        //Check if returned list items are same those in the database
        foreach (var listItem in listItems)
        {
            var header = listItem.QuerySelector(".list-group-item-heading");
            var taskTitle = header.InnerHtml.Trim();
            tasksInDatabase.Any(t => t.Title == taskTitle).ShouldBeTrue();
        }
    }
}


You can check the HTML deeper and in more detailed. But in most cases, checking the fundamental tags will be enough.

LINK: https://www.codeproject.com/articles/1115763/using-asp-net-core-entity-framework-core-and-asp-n

.NET Core Datagrid

.Net Core datagrid with server side paging, sorting and filtering

Showing data in grid format is an important task for many web applications. This blog gives a demo of how to display data with the Bootstrap Table plug in. The demo shows advanced features like server side paging, filtering and sorting.

With the demo application, I cover these aspects in more detail:

  1. Setup Bootstrap Table plug in
  2. Setup DataSource
  3. Table definition in cshtml file
  4. Custom cell rendering
  5. Server side paging, sorting and filtering
  6. Highlight selected row
  7. Custom Toolbar
  8. Additional Load parameters

Bootstrap Table plug in

There are many data grid packages available, all with their own strengths and weaknesses. In this demo, I use the Bootstrap Table plug in. It's a free plug in with useful features:

  1. Ajax enabled
  2. Server side paging, filtering and sorting
  3. Easy to use
  4. Lightweight and fast
  5. Bootstrap support
  6. Third party packages are available for extra functionality

Setup Bootstrap Table plug in

Start Visual Studio and create a new .NET Core Project with no authentication.

Bootstrap Table is a JavaScript library and you only need to include the library files in the application. It has two files, a JavaScript and a CSS file. The rendersection is set at the end of the _Layout.cshtml file. The rendersection provides a hook to an individual cshtml page. This hook is executed during the rendering of the individual cshtml page.

  ...
  @RenderSection("scripts", required: false)
</body>
</html>
...
@section scripts { 
  @await Html.PartialAsync("bootstraptable")
 
  <script type="text/javascript">
  ...

This chain renders the shared bootstraptable.cshtml, located in the shared views folder. This makes re-use in other pages easy and the files are just in time loaded for optimal performance. Dot Net Core offers a neat solution to differentiate between production and development files. In development, you can use the larger, easy readable files, while for production, you automatically switch to the smaller and faster, minified files.

<environment names="Development">
  <link rel="stylesheet" href="~/css/bootstrap-table.css">
  <script src="~/lib/bootstrap-table/bootstrap-table.js"></script>
</environment>
<environment names="Staging,Production">
  <link rel="stylesheet" 
   href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.js">
  </script>
</environment>


Setup DataSource

In most cases, the data is retrieved from database server or a REST-api. This demo focuses on how to show data and less on how to get data. In this case, the data is fetched from a simple JSON file, located at the web server. This keeps the datasource simple and easy to setup. The controller has a private IList<Country> countries property. Country is a simple POCO class:

public class Country
{
  [Key()]
  public Int32 Code { get; set; }

  [StringLength(2, MinimumLength =2)]
  [Display(Name="2 Digit ISO")]
  public String ISO2{ get; set; }

  [StringLength(3, MinimumLength = 3)]
  [Display(Name = "3 Digit ISO")]
  public String ISO3 { get; set; }

  public String Name { get; set; }
}
 

Entity Framework Support

This sample uses IList<Country> as datasource, but it can easily be replaced by a DbSet<Country> delivered by the Entity Framework. The demo works with every kind of datasource, provided the datasource can be cast to the none generic IQueryable type.

Table Definition in cshtml File

You can setup the table definition in either HTML or jquery. I prefer HTML because I find it easier to read. A combination is also possible. In this demo, HTML sets the table layout and the table events are handled in jquery. Data attributes in the <table> section sets the table behavior, the columns and rows are configured in the <tr> section.

<table id="table"
         data-unique-id="Code"
         data-sort-name="Code"
         data-sort-order="asc"
         data-classes="table table-condensed table-hover table-striped"
         data-toggle="table"
         data-side-pagination="server"
         data-url="Load"
         data-pagination="true"
         data-search="true"
         data-show-refresh="true"
         data-toolbar="#toolbar"
         data-page-size="20"
         data-page-list="[5,10,20,50,100,All]">
    <thead>
      <tr>
        <th data-field="ISO2" data-sortable="false" 
        data-halign="center" data-align="center" 
         data-formatter="flagFormatter">Flag</th>
        <th data-field="Code" data-sortable="true" 
        data-halign="center" data-align="center">Code</th>
        <th data-field="ISO3" data-sortable="true">ISO 3</th>
        <th data-field="Name" data-sortable="true">Name</th>
      </tr>
    </thead>
  </table>


Column settings like alignment, width, date and number formats can be configured with attributes. The data-unique-id="Code" sets the primary key column. During rendering, each table row gets a data-uniqueidattribute with its key value.

<tr data-uniqueid="4" data-index="0">
  ...
</tr>

This key attribute is vital for CRUD (Create, Retrieve, Update and Delete) operations. With jquery, you can retrieve the key value from a row with little effort. If the data-unique-id is empty or not set, the table will still be rendered. The data-sort-name and data-sort-order attributes set the initial sort column and order. These values are passed to the controller during the data request.

Custom Cell Rendering

Bootstrap Table supports custom cell rendering with the data-formatter attribute. This offers maximum flexibility if the standard configuration options are not enough.

function flagFormatter(value, row) {
     return '<img src="/images/flags/' + value.toLowerCase() + '.png" >';
   }
 

The value parameter is the column value and row parameter contains all the row values.

Server Side Paging, Sorting and Filtering

It takes only a few attributes to setup server side paging, sorting and filtering.

data-side-pagination="server"
data-url="Load"
data-pagination="true"
data-search="true"

The data-url="Load" attribute specifies that the controller class has a public Load function. The Loadfunction handles the input parameters and returns a JSON document.

[HttpGet]
public virtual ActionResult Load(String sort, String order, String search, Int32 limit, Int32 offset)
{
   // Get entity fieldnames
   List<String> columnNames = typeof(Country).GetProperties(BindingFlags.Public | 
                              BindingFlags.Instance).Select(p => p.Name).ToList();

   // Create a separate list for searchable field names   
   List<String> searchFields = new List<String>(columnNames);

   // Exclude field Iso2 for filtering 
   searchFields.Remove("ISO2");

   // Perform filtering
   IQueryable items = SearchItems(countries.AsQueryable(), search, searchFields);

   // Sort the filtered items and apply paging
   return Content(ItemsToJson
   (items, columnNames, sort, order, limit, offset), "application/json");
}


Input parameters:

  • sort: sort column name
  • order: sort direction, asc or desc
  • search: search argument entered by user
  • limit: page size
  • offset: number of records to skip before fetching the data page

JSON Output:

  • total: number of records available after filtering
  • rows: array with country records

The array capacity is equal to or less than the limit page size.

"total": 193,
"rows": [
  {
    "Code": 4,
    "ISO2": "AF",
    "ISO3": "AFG",
    "Name": "Afghanistan"
  },
  {
    "Code": 8,
    "ISO2": "AL",
    "ISO3": "ALB",
    "Name": "Albania"
  },
 ...
 

Exclude Filter Fields

Field ISO2 is used for rendering the flag image, the code itself is not visible for the user. In this GUI design, the search parameter applies only for visible data. This means that the ISO2 property must be excluded from searchable fields.

// Get entity fieldnames
List<String> columnNames = typeof(Country).GetProperties(BindingFlags.Public | 
                           BindingFlags.Instance).Select(p => p.Name).ToList();

// Create a separate list for searchable field names   
List<String> searchFields = new List<String>(columnNames);

// Exclude field Iso2 for filtering 
searchFields.Remove("ISO2");

// Perform filtering
IQueryable items = SearchItems(countries.AsQueryable(), search, searchFields);


Reusable Filtering with Dynamic Linq

Linq is a great innovation. It gives the ability to execute queries on an enumerable collection. Unlike SQL, Linq has compile time syntax checking. This is helpful in most cases. It already detects errors during compile time instead of runtime. If I want to create a reusable filter method, the compile time syntax becomes an obstacle. Different entities has different fieldnames, so how to pass different field names to one reusable method? Runtime parsing instead of compile time parsing is the solution. The Dynamic Linq Core packages does just this. It smoothly integrates with Linq and provides extra overload functions for where clauses, sorting and other operations. Dynamic Linq is used in SearchItems to create a searchExpression at runtime.

protected virtual IQueryable SearchItems(IQueryable items, String search, List<String> columnNames)
{
  // Apply filtering to all visible column names
  if (search != null && search.Length > 0)
  {
    StringBuilder sb = new StringBuilder();

    // create dynamic Linq expression
    foreach (String fieldName in columnNames)
      sb.AppendFormat("({0} == null ? 
      false : {0}.ToString().IndexOf(@0, @1) >=0) or {1}", 
                                      fieldName, Environment.NewLine);

    String searchExpression = sb.ToString();
    // remove last "or" occurrence
    searchExpression = searchExpression.Substring
    (0, searchExpression.LastIndexOf("or"));

    // Apply filtering, 
    items = items.Where
    (searchExpression, search, StringComparison.OrdinalIgnoreCase);
  }
  return items;
}


Country searchExpression generated by SearchItems:

(Code == null ? false : Code.ToString().IndexOf(@0, @1) >=0) or 
(ISO3 == null ? false : ISO3.ToString().IndexOf(@0, @1) >=0) or 
(Name == null ? false : Name.ToString().IndexOf(@0, @1) >=0)

Please note the ISO2 field is not in the searchExpression as expected. In this demo, the SearchItemsimplementation is pretty straightforward. If a more complicated filtering is required, the SearchItems can be overridden to meet the new needs.

Generating JSON Document

The ItemsToJson function creates the JSON document that is consumed by the Bootstrap Table.

protected String ItemsToJson(IQueryable items, List<String> columnNames, 
                             String sort, String order, Int32 limit, Int32 offset)
{
  try
  {
	// where clause is set, count total records
	Int32 count = items.Count();

	// Skip requires sorting, so make sure there is always sorting
	String sortExpression = "";
   
	if (sort != null && sort.Length > 0)
	  sortExpression += String.Format("{0} {1}", sort, order);

	// show all records if limit is not set
	if (limit == 0)
	  limit = count;

	// Prepare json structure
	var result = new
	{
	  total = count,
	  rows = items.OrderBy(sortExpression).Skip(offset).Take(limit).Select
             ("new (" + String.Join(",", columnNames) + ")")
	};

	return JsonConvert.SerializeObject(result, Formatting.None, new JsonSerializerSettings() 
                  { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
  }
  catch (Exception ex)
  {
	Console.WriteLine(ex.Message);
	return null;
  }
}


Input parameters:

  • items: unsorted, filtered entity set
  • columnNames: fields included in JSON document
  • sort: sort column name
  • order: sort direction, asc or desc
  • search: search argument entered by user
  • limit: page size
  • offset: number of records to skip before fetching the data page

The columnNames variable limits the number of properties exposed in the JSON document. This can be useful if you don't want to show all available entities properties for performance or security reasons. The paging requires sorting and is provided by Dynamic Linq. The paging is implemented with the standard Linq Skip and Takefunctions. The option Formatting.None reduces the JSON document size, and increases performance, but makes it more difficult to read. I only use the option Formatting.Indented for debugging purposes.

Highlight Selected Row

Bootstrap Table has several row selection options. It can even remember selected rows on a previous page, which I think is pretty cool. You can read the documentation on how this can be done. In the demo application, I use jquery and CSS. On forums, there are many questions about this topic, so let's give it some attention here. First, I modified the CSS style to make the selected row more apparent. I could overwrite the bootstrap CSS file, but all the work would be lost in case of an update. Setting the new style in the site.css file frees you from this risk.

/* selected row style */
.table-striped tbody .highlight td {
  background-color: #b5b5b5;
}

The next step is to attach the Bootstrap Table row click event to the highLightRow function.

// register row-click event
$('#table').on('click-row.bs.table', function ($element, row, $tr) {
  highLightRow($tr);
});


The highLightRow function assigns the highlight CSS class to the selected row and removes the CSS class from all other rows. This makes sure that only one row at a time is selected.

function highLightRow($tr) {
   $tr.addClass('highlight').siblings().removeClass('highlight');
}


Custom Toolbar

With Bootstrap Table, you can customize the toolbar with just plain HTML. In some other packages, you need to know a lot about the grid details and its API. Bootstrap Table is breathtakingly simple. Create your toolbar buttons and other controls inside a div with an id. Assign this id to the data-toolbar attribute and that's all it takes!

<div id="toolbar">
  <button id="btninfo" title="Show selected row info" class="btn btn-default" type="button">
  <i class="glyphicon glyphicon-info-sign"></i> row info</button>
</div>>
 
<table id="table"
 ...
 data-toolbar="#toolbar"
 ...

Additional Load Parameters

Sometimes, the GUI requires that additional parameters are sent to the controller. This takes only a few simple steps. First, set what function injects the extra parameter with the data-query-params attribute.

<table id="table"
 ...
 data-query-params="extraServerParams"
 ...

In this demo, the extra parameter is fixed, normally, you would use the value of an input control.

function extraServerParams(params) {
   params.ExtraParam = 2;
   return params;
}

The last step is modifying the Load function on the controller to process the extra parameter.

Conclusion

Displaying data in a grid is an important requirement for many applications. Bootstrap Table does an excellent job at this. It's easy to setup and use and works well with Bootstrap and Dot Net Core. Dynamic Linq makes the solution highly reusable. I added the demo application so you can play with it. If you have any comments or questions, please let me know.

Further Reading

LINK: https://www.codeproject.com/Articles/1166225/NET-Core-Datagrid

Creating Angular2 Application with ASP.NET Core Template Pack in VS 2015

Creating Angular2 Application with ASP.NET Core Template Pack in VS 2015

Introduction

Let's create a Web Application with ASP.NET Core Template Pack in VS 2015, this template includes all configurations to work with Angular2 and ASP.NET Core

Background

Related to Web applications development, we need to integrate RESTful APIs with UI, now there is a final release for Angular2, so we'll work developing a web application that integrates ASP.NET Core with Angular2, so this template includes all configurations we need to develop a web application.

Skills Prerequisites

  • C#
  • ORM (Object Relational Mapping)
  • RESTful services
  • TypeScript
  • Angular 2

Software Prerequisites

  • Visual Studio 2015 with Update 3
  • ASP.NET Core Template Pack Download link
  • AdventureWorks database download
  • Node JS

Using the Code

Download and install the template for your Visual Studio (Check software prerequisites), thanks for Oscar Agreda for share the template's download link.

Step 01 - Create Project in Visual Studio

Once we have the template added to our Visual Studio open Visual Studio and create a new project:

New project

Set the project name OrderViewer and click OK.

Once we have project created, we can run the project and we'll get the following output:

First Project run

Step 02 - Add Back-end Code

If we don't have knowledge about how to configure Web API for accessing SQL Server instance with EF Core, please take a look on related links section.

At this moment, we'll work with Sales schema: Order header and details for showing order's information.

We need to create the following directories for project:

  • Extensions: Placeholder for extension methods
  • Models: Placeholder for objects related for database access, modeling and configuration
  • Responses: Placeholder for objects that represent Http responses

The code at this point would be:

SalesController.cs class code:

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OrderViewer.Core.DataLayer.Contracts;
using OrderViewer.Extensions;
using OrderViewer.Responses;
using OrderViewer.ViewModels;

namespace OrderViewer.Controllers
{
    [Route("api/[controller]")]
    public class SalesController : Controller
    {
        private ISalesRepository SalesRepository;

        public SalesController(ISalesRepository repository)
        {
            SalesRepository = repository;
        }

        protected override void Dispose(Boolean disposing)
        {
            SalesRepository?.Dispose();

            base.Dispose(disposing);
        }

        /// <summary>
        /// Retrieves a list of orders that match with criteria
        /// </summary>
        /// <param name="pageSize">Page size</param>
        /// <param name="pageNumber">Page number</param>
        /// <param name="salesOrderNumber">Sales order number</param>
        /// <param name="customerName">Customer name</param>
        /// <returns>A ListModelResponse of OrderSummaryViewModel</returns>
        [HttpGet("Order")]
        public async Task<IActionResult> GetOrders(Int32? pageSize = 10, Int32? pageNumber = 1, String salesOrderNumber = "", String customerName = "")
        {
            var response = new ListModelResponse<OrderSummaryViewModel>() as IListModelResponse<OrderSummaryViewModel>;

            try
            {
                response.PageSize = (Int32)pageSize;
                response.PageNumber = (Int32)pageNumber;

                var list = await SalesRepository
                        .GetOrders((Int32)pageSize, (Int32)pageNumber, salesOrderNumber, customerName)
                        .ToListAsync();

                response.Model = list.Select(item => item.ToOrderSummaryViewModel());

                response.Message = String.Format("Total of records: {0}", response.Model.Count());
            }
            catch (Exception ex)
            {
                response.DidError = true;
                response.ErrorMessage = ex.Message;
            }

            return response.ToHttpResponse();
        }

        /// <summary>
        /// Retrieves an existing order by id
        /// </summary>
        /// <param name="id">Order ID</param>
        /// <returns>A SingleModelResponse of OrderHeaderViewModel</returns>
        [HttpGet("Order/{id}")]
        public async Task<IActionResult> GetOrder(Int32 id)
        {
            var response = new SingleModelResponse<OrderHeaderViewModel>() as ISingleModelResponse<OrderHeaderViewModel>;

            try
            {
                var entity = await SalesRepository.GetOrderAsync(id);

                response.Model = entity.ToOrderHeaderViewModel();
            }
            catch (Exception ex)
            {
                response.DidError = true;
                response.ErrorMessage = ex.Message;
            }

            return response.ToHttpResponse();
        }
    }
}
 

ISalesRepository.cs interface code:

using System;
using System.Linq;
using System.Threading.Tasks;
using OrderViewer.Core.DataLayer.DataContracts;
using OrderViewer.Core.EntityLayer;

namespace OrderViewer.Core.DataLayer.Contracts
{
    public interface ISalesRepository : IRepository
    {
        IQueryable<OrderSummary> GetOrders(Int32 pageSize, Int32 pageNumber, String salesOrderNumber, String customerName);

        Task<SalesOrderHeader> GetOrderAsync(Int32 orderID);
    }
}
AdventureWorksDbContext.cs class code:

Hide   Shrink    Copy Code
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace OrderViewer.Models
{
    public class AdventureWorksDbContext : Microsoft.EntityFrameworkCore.DbContext
    {
        public AdventureWorksDbContext(IOptions<AppSettings> appSettings, IEntityMapper entityMapper)
        {
            ConnectionString = appSettings.Value.ConnectionString;
            EntityMapper = entityMapper;
        }

        public String ConnectionString { get; }

        public IEntityMapper EntityMapper { get; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(ConnectionString);

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            EntityMapper.MapEntities(modelBuilder);

            base.OnModelCreating(modelBuilder);
        }
    }
}


SalesRepository.cs class code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using OrderViewer.Core.DataLayer.Contracts;
using OrderViewer.Core.DataLayer.DataContracts;
using OrderViewer.Core.EntityLayer;

namespace OrderViewer.Core.DataLayer.Repositories
{
    public class SalesRepository : Repository, ISalesRepository
    {
        public SalesRepository(AdventureWorksDbContext dbContext)
            : base(dbContext)
        {
        }

        public IQueryable<OrderSummary> GetOrders(Int32 pageSize, Int32 pageNumber, String salesOrderNumber, String customerName)
        {
            var query =
                from orderHeader in DbContext.Set<SalesOrderHeader>()
                join customer in DbContext.Set<Customer>()
                    on orderHeader.CustomerID equals customer.CustomerID
                join customerPersonJoin in DbContext.Set<Person>()
                    on customer.PersonID equals customerPersonJoin.BusinessEntityID
                        into customerPersonTemp
                from customerPerson in customerPersonTemp.Where(relation => relation.BusinessEntityID == customer.PersonID).DefaultIfEmpty()
                join customerStoreJoin in DbContext.Set<Store>()
                    on customer.StoreID equals customerStoreJoin.BusinessEntityID
                        into customerStoreTemp
                from customerStore in customerStoreTemp.Where(relation => relation.BusinessEntityID == customer.StoreID).DefaultIfEmpty()
                select new OrderSummary
                {
                    SalesOrderID = orderHeader.SalesOrderID,
                    OrderDate = orderHeader.OrderDate,
                    DueDate = orderHeader.DueDate,
                    ShipDate = orderHeader.ShipDate,
                    SalesOrderNumber = orderHeader.SalesOrderNumber,
                    CustomerID = orderHeader.CustomerID,
                    CustomerName = customerPerson.FirstName + (customerPerson.MiddleName == null ? String.Empty : " " + customerPerson.MiddleName) + " " + customerPerson.LastName,
                    StoreName = customerStore == null ? String.Empty : customerStore.Name,
                    Lines = orderHeader.SalesOrderDetails.Count(),
                    TotalDue = orderHeader.TotalDue
                };

            if (!String.IsNullOrEmpty(salesOrderNumber))
            {
                query = query.Where(item => item.SalesOrderNumber.ToLower().Contains(salesOrderNumber.ToLower()));
            }

            if (!String.IsNullOrEmpty(customerName))
            {
                query = query.Where(item => item.CustomerName.ToLower().Contains(customerName.ToLower()));
            }

            if (String.IsNullOrEmpty(salesOrderNumber) && String.IsNullOrEmpty(customerName))
            {
                query = query.OrderByDescending(item => item.SalesOrderID);
            }

            return query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
        }

        public Task<SalesOrderHeader> GetOrderAsync(Int32 orderID)
        {
            var entity = DbContext
                .Set<SalesOrderHeader>()
                .Include(p => p.CustomerFk.PersonFk)
                .Include(p => p.CustomerFk.StoreFk)
                .Include(p => p.SalesPersonFk)
                .Include(p => p.SalesTerritoryFk)
                .Include(p => p.ShipMethodFk)
                .Include(p => p.BillAddressFk)
                .Include(p => p.ShipAddressFk)
                .Include(p => p.SalesOrderDetails)
                    .ThenInclude(p => p.ProductFk)
                .FirstOrDefaultAsync(item => item.SalesOrderID == orderID);

            return entity;
        }
    }
}
 

We can return an anonymous type but we want to add unit testing soon, it's better to have a typed structure because in that way it's more easy to know which information we share with our client

One we have the back-end build with no errors, we can test the following urls in our browser:

Web API Urls
UrlDescription
api/Sales/OrderRetrieve all orders with default page size and page number
api/Sales/Order?salesOrderNumber=so7Retrieve all orders match with sales order number "so72"
api/Sales/Order?customerName=herRetrieve all orders match with customer name "hey"
api/Sales/Order?salesOrderNumber=so72&customerName=haRetrieve all orders match with sales order number "so72" and customer name "her"
api/Sales/Order/75123Retrieve one order by id 75123

Step 03 - Add Help Page for API

Once we have the API, now we proceed to add a help page for API, first we need to add the following package for API project:

Package nameVersion
Swashbuckle6.0.0-beta902

Now save changes and build the project, now we proceed to apply the changes in Starup class in order to enable Swagger in project.

Startup class code:

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using OrderViewer.Core.DataLayer;
using OrderViewer.Core.DataLayer.Contracts;
using OrderViewer.Core.DataLayer.Mapping;
using Swashbuckle.Swagger.Model;

namespace OrderViewer
{
    public class Startup
    {
        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)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            services.AddEntityFrameworkSqlServer().AddDbContext<AdventureWorksDbContext>();

            services.AddScoped<IEntityMapper, AdventureWorksEntityMapper>();
            services.AddScoped<ISalesRepository, SalesRepository>();
            services.AddScoped<IProductionRepository, ProductionRepository>();

            services.AddOptions();

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

            services.AddSingleton<IConfiguration>(Configuration);

            services.AddSwaggerGen();

            services.ConfigureSwaggerGen(options =>
            {
                options.SingleApiVersion(new Info
                {
                    Version = "v1",
                    Title = "OrderViewer API",
                    Description = "OrderViewer ASP.NET Core Web API",
                    TermsOfService = "None",
                    Contact = new Contact { Name = "C. Herzl", Email = "", Url = "https://twitter.com/hherzl" },
                    License = new License { Name = "Use under LICX", Url = "http://url.com" }
                });

                //Determine base path for the application.
                var basePath = PlatformServices.Default.Application.ApplicationBasePath;

                //Set the comments path for the swagger json and ui.
                var xmlPath = Path.Combine(basePath, "OrderViewer.xml");

                options.IncludeXmlComments(xmlPath);
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
                {
                    HotModuleReplacement = true
                });
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseSwagger();

            app.UseSwaggerUi();
        }
    }
}
 

Save changes and run the project, now make sure that your methods in controllers have xml comments and API project has XML documentation enabled, as last step we can test the following urls:

UrlDescription
http://localhost:[random_port]/swagger/v1/swagger.jsonGets json file with API description
http://localhost:[random_port]/swagger/uiShows a graphic interface with API description

Swagger UI:

Swagger UI

Production Operations:

Production Operations

Sales Operations:

Sales Operations

Step 04 - Add Front-end Code

Next, add the following code into app directory, as we can see all front-end code is TypeScript, now we can talk about what is TypeScript and why we should to use it.

  1. What is TypeScript? According to Anders H. director of C# development, TypeScript it's a super set of JavaScript, allow to us create classes, interfaces, typed objects and others functions like Java/C#
  2. Can I use JavaScript for Angular2? Yes, in fact we can use pure JavaScript, Google Dart or TypeScript but by default Google recommends to use TypeScript
  3. Why Google uses Microsoft's technology instead of maximize Google dart? I think both companies have an agreement but until now I don't have more details about this only speculation
  4. This technology adoption supposes a treason from Google for open source developers? I don't think so, all companies have the interest to lead the development industry, thinking about this change as treason it's a wrong idea, it's better embrace the new technology as a technical goal :)

Go to app directory in Solution Explorer and remove all related to counter and fetch data and proceed to create the following files:

  1. app/components/sales/order-list.component.html
  2. app/components/sales/order-list.component.ts
  3. app/components/sales/order-detail.component.html
  4. app/components/sales/order-detail.component.ts
  5. app/responses/list.response.ts
  6. app/responses/single.response.ts
  7. app/models/order.detail.ts
  8. app/models/order.summary.ts
  9. app/models/order.ts
  10. app/services/sales.service.ts

Front-end code

About TypeScript:

  • We can import any reference in typescript with import keyword
  • If we want to access all members from class or interface we need to add export keyword
  • Declaration in typescript is: name: type (e.g. firstName: string)
  • Naming convention in typescript it's more javascript and java

order-list.component.html file code:

<h1>Orders</h1>

<fieldset>
    <legend>Search</legend>

    <div class="form-inline">
        <div class="form-group">
            <input type="text" class="form-control" placeholder="Sales Order Number" [(ngModel)]="salesOrderNumber" />
        </div>

        <div class="form-group">
            <input type="text" class="form-control" placeholder="Customer Name" [(ngModel)]="customerName" />
        </div>

        <div class="form-group">
            <select id="pageSize" name="pageSize" class="form-control list-box tri-state" [(ngModel)]="pageSize">
                <option value="10" selected="selected">10</option>
                <option value="25">25</option>
                <option value="50">50</option>
                <option value="100">100</option>
            </select>
        </div>

        <div class="form-group">
            <button type="button" class="btn btn-primary glyphicon glyphicon-search" (click)="search()"></button>
        </div>
    </div>
</fieldset>

<br />

<div class="alert alert-info" role="alert" *ngIf="result">
    <span class="glyphicon glyphicon-info-sign"></span>
    <strong>{{ result.message }}</strong>
</div>

<table class="table table-hover" *ngIf="result">
    <tr>
        <th>#</th>
        <th>Order Date</th>
        <th>Ship Date</th>
        <th>Due Date</th>
        <th>Sales Order #</th>
        <th>Customer</th>
        <th>Store</th>
        <th>Total Due</th>
        <th>Lines</th>
        <th></th>
    </tr>
    <tr *ngFor="let item of result.model">
        <td>
            {{ item.salesOrderID }}
        </td>
        <td>
            {{ item.orderDate | date: "shortDate" }}
        </td>
        <td>
            {{ item.shipDate | date: "shortDate" }}
        </td>
        <td>
            {{ item.dueDate | date: "shortDate" }}
        </td>
        <td>
            {{ item.salesOrderNumber }}
        </td>
        <td>
            {{ item.customerName }}
        </td>
        <td>
            {{ item.storeName }}
        </td>
        <td style="text-align: right;">
            {{ item.totalDue | currency }}
        </td>
        <td style="text-align: right;">
            {{ item.lines }}
        </td>
        <td>
            <div class="btn-group">
                <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">
                    <span class="caret"></span>
                </button>
                <ul class="dropdown-menu">
                    <li><a (click)="details(item)">Details</a></li>
                </ul>
            </div>
        </td>
    </tr>
</table>
 

order-list.component.ts file code:

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { IListResponse } from "../../responses/list.response";
import { OrderSummary } from "../../models/order.summary";
import { SalesService } from "../../services/sales.service";

@Component({
    selector: "order-list",
    template: require("./order-list.component.html")
})
export class OrderListComponent implements OnInit {
    public pageNumber: number;
    public pageSize: number;
    public salesOrderNumber: string;
    public customerName: string;
    public result: IListResponse<OrderSummary>;

    constructor(private router: Router, private service: SalesService) {
    }

    ngOnInit(): void {
        this.search();
    }

    search(): void {
        this.service
            .getOrders(this.pageNumber, this.pageSize, this.salesOrderNumber, this.customerName)
            .subscribe(result => {
                this.result = result.json();
            });
    }

    details(order: OrderSummary): void {
        this.router.navigate(["/order-detail/", order.salesOrderID]);
    }
}
 

order-detail.component.html file code:

<h2>Order Detail</h2>
<style>
    .dl-horizontal dt {
        clear: left;
        float: left;
        overflow: hidden;
        text-align: right;
        text-overflow: ellipsis;
        width: 170px;
        white-space: nowrap;
  }
</style>

<div *ngIf="result">
    <div>
        <dl class="dl-horizontal">
            <dt>Revision Number</dt>
            <dd>{{ result.model.revisionNumber }}</dd>

            <dt>Order Date</dt>
            <dd>{{ result.model.orderDate | date: "short" }}</dd>

            <dt>Due Date</dt>
            <dd>{{ result.model.dueDate | date: "short" }}</dd>

            <dt>Ship Date</dt>
            <dd>{{ result.model.shipDate | date: "short" }}</dd>

            <dt>Sales Order Number</dt>
            <dd>{{ result.model.salesOrderNumber }}</dd>

            <dt>Purchase Order Number</dt>
            <dd>{{ result.model.purchaseOrderNumber }}</dd>

            <dt>Account Number</dt>
            <dd>{{ result.model.accountNumber }}</dd>

            <dt>Customer Name</dt>
            <dd>{{ result.model.customerName }}</dd>

            <dt>Store Name</dt>
            <dd>{{ result.model.storeName }}</dd>

            <dt>Sales Person Name</dt>
            <dd>{{ result.model.salesPersonName }}</dd>

            <dt>Territory Name</dt>
            <dd>{{ result.model.territoryName }}</dd>

            <dt>Ship Method</dt>
            <dd>{{ result.model.shipMethodName }}</dd>

            <dt>Sub Total</dt>
            <dd>{{ result.model.subTotal | currency }}</dd>

            <dt>Tax Amt</dt>
            <dd>{{ result.model.taxAmt | currency }}</dd>

            <dt>Freight</dt>
            <dd>{{ result.model.freight | currency }}</dd>

            <dt>Total Due</dt>
            <dd>{{ result.model.totalDue | currency }}</dd>

            <dt>Comment</dt>
            <dd>{{ result.model.comment }}</dd>

            <dt>Modified Date</dt>
            <dd>{{ result.model.modifiedDate | date: "short" }}</dd>
        </dl>
    </div>

    <h3>Billing & Shipping</h3>

    <table class="table table-hover">
        <tr>
            <th colspan="2">Bill Address</th>
            <th colspan="2">Ship Address</th>
        </tr>
        <tr>
            <td>Address line 1</td>
            <td>{{ result.model.billAddress.addressLine1 }}</td>
            <td>Address line 1</td>
            <td>{{ result.model.shipAddress.addressLine1 }}</td>
        </tr>
        <tr>
            <td>Address line 2</td>
            <td>{{ result.model.billAddress.addressLine2 }}</td>
            <td>Address line 2</td>
            <td>{{ result.model.shipAddress.addressLine2 }}</td>
        </tr>
        <tr>
            <td>City</td>
            <td>{{ result.model.billAddress.city }}</td>
            <td>City</td>
            <td>{{ result.model.shipAddress.city }}</td>
        </tr>
        <tr>
            <td>Postal code</td>
            <td>{{ result.model.billAddress.postalCode }}</td>
            <td>Postal code</td>
            <td>{{ result.model.shipAddress.postalCode }}</td>
        </tr>
    </table>

    <h3>Details</h3>

    <table class="table table-hover">
        <tr>
            <th>Product name</th>
            <th style="text-align: right;">
                Unit price
            </th>
            <th style="text-align: right;">
                Quantity
            </th>
            <th style="text-align: right;">
                Unit price discount
            </th>
            <th style="text-align: right;">
                Line total
            </th>
        </tr>
        <tr *ngFor="let item of result.model.orderDetails">
            <td>
                {{ item.productName }}
            </td>
            <td style="text-align: right;">
                {{ item.unitPrice | currency }}
            </td>
            <td style="text-align: right;">
                {{ item.orderQty }}
            </td>
            <td style="text-align: right;">
                {{ item.unitPriceDiscount }}
            </td>
            <td style="text-align: right;">
                {{ item.lineTotal | currency }}
            </td>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td style="text-align: right;">
                <strong>
                    {{ result.model.total | currency }}
                </strong>
            </td>
        </tr>
    </table>
</div>

<p>
    <a (click)="backToList()">Back to list</a>
</p>
 

order-detail.component.ts file code:

import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { Location } from "@angular/common";
import { ISingleResponse } from "../../responses/single.response";
import { Order } from "../../models/order";
import { SalesService } from "../../services/sales.service";

@Component({
    selector: "order-detail",
    template: require("./order-detail.component.html")
})
export class OrderDetailComponent implements OnInit {
    public result: ISingleResponse<Order>;

    constructor(private route: ActivatedRoute,
        private location: Location,
        private router: Router,
        private service: SalesService) {
    }

    ngOnInit(): void {
        this.loadData();
    }

    loadData(): void {
        this.route.params.forEach((params: Params) => {
            let id = +params["id"];

            this.service.getOrder(id).subscribe(result => {
                this.result = result.json();
            });
        });
    }

    backToList(): void {
        this.router.navigate(["/order"]);
    }
}
 

sales.service.ts file code:

import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Response } from "@angular/http";
import { Observable } from "rxjs/Observable";

export interface ISalesService {
    getOrders(pageNumber: number,
        pageSize: number,
        salesOrderNumber: string,
        customerName: string): Observable<Response>;

    getOrder(id: number): Observable<Response>;
}

@Injectable()
export class SalesService implements ISalesService {
    constructor(public http: Http) {
    }

    getOrders(pageNumber: number,
        pageSize: number,
        salesOrderNumber: string,
        customerName: string): Observable<Response> {
        var url: string = "/api/Sales/Order?" +
            "pageNumber=" + (pageNumber ? pageNumber : 1) +
            "&pageSize=" + (pageSize ? pageSize : 10) +
            "&salesOrderNumber=" + (salesOrderNumber ? salesOrderNumber : "") +
            "&customerName=" + (customerName ? customerName : "");

        return this.http.get(url);
    }

    getOrder(id: number): Observable<Response> {
        return this.http.get("/api/Sales/Order/" + id);
    }
}
 

Please take care about these aspects:

  1. We have created a service for consuming our Web API instead of inject Http inside of component, this is because it's more reusable to have an injected service and use in different components instead of copy/paste logic to consume Web API.
  2. app.module.ts is the core of our application and inside of this file we need to import all components and define the route table.
  3. In app.module.ts file, the providers array determinates all injected services for whole application.
  4. In TypeScript we don't forget export keyword to expose class or interface that would be use by other objects.
  5. To write clean code, we have specific directories to specific objects: models, services, components, etc.
  6. Naming convention for TypeScript + Angular2 files is lower case and dash plus suffix according to object's type: component, service, etc.
  7. We can format dates and numbers in angular with filters (e.g. {{ value | date }})

Save all changes and build solution, if the build have no errors, we can run the project and see the following output on our browser:

Success run

Now, please clic on details for one order to view order details:

Order details view

For now, this is enough in client side because we need to understand the basic aspects of TypeScript & Angular2, really aren't not basic :) ... Create a service and inject to component it's an advanced development style and requires a lot of concepts and skills to getting the advantages of this programming style. Soon I'll add the missing features for this application, please feel free to add your suggestions to improve this code.

Step 05 - Add Unit Tests for Back-end

At this point we'll add unit tests for our back-end code in order to automatize testing in the code.

Open a command line and navigate to solution's directory and execute these commands

  1. mkdir OrderViewer.Tests
  2. cd OrderViewer.Tests
  3. dotnet new -t xunittest
  4. dotnet restore

Now we should add our tests project in solution from Visual Studio, once we have added now we proceed to rename the default generated class to SalesControllerTests.cs and add change the code to this:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using OrderViewer.Controllers;
using OrderViewer.Core.DataLayer.DataContracts;
using OrderViewer.Responses;
using Xunit;

namespace OrderViewer.Tests
{
    public class SalesControllerTests
    {
        [Fact]
        public async Task TestGetOrdersAsync()
        {
            using (var repository = RepositoryMocker.GetSalesRepository())
            {
                // Arrange
                var controller = new SalesController(repository);

                // Act
                var response = await controller.GetOrders() as ObjectResult;

                // Assert
                var value = response.Value as IListModelResponse<OrderSummaryViewModel>;

                Assert.False(value.DidError);
            }
        }

        [Fact]
        public async Task TestGetOrdersSearchingBySalesOrderNumberAsync()
        {
            using (var repository = RepositoryMocker.GetSalesRepository())
            {
                // Arrange
                var controller = new SalesController(repository);
                var salesOrderNumber = "so72";

                // Act
                var response = await controller.GetOrders(salesOrderNumber: salesOrderNumber) as ObjectResult;

                // Assert
                var value = response.Value as IListModelResponse<OrderSummaryViewModel>;

                Assert.False(value.DidError);
            }
        }

        [Fact]
        public async Task TestGetOrdersSearchingByCustomerNameAsync()
        {
            using (var repository = RepositoryMocker.GetSalesRepository())
            {
                // Arrange
                var controller = new SalesController(repository);
                var customerName = "her";

                // Act
                var response = await controller.GetOrders(customerName: customerName) as ObjectResult;

                // Assert
                var value = response.Value as IListModelResponse<OrderSummaryViewModel>;

                Assert.False(value.DidError);
            }
        }

        [Fact]
        public async Task TestGetOrdersSearchingBySalesOrderNumberAndCustomerNameAsync()
        {
            using (var repository = RepositoryMocker.GetSalesRepository())
            {
                // Arrange
                var controller = new SalesController(repository);
                var salesOrderNumber = "so72";
                var customerName = "her";

                // Act
                var response = await controller.GetOrders(salesOrderNumber: salesOrderNumber, customerName: customerName) as ObjectResult;

                // Assert
                var value = response.Value as IListModelResponse<OrderSummaryViewModel>;

                Assert.False(value.DidError);
            }
        }

        [Fact]
        public async Task TestGetOrderAsync()
        {
            using (var repository = RepositoryMocker.GetSalesRepository())
            {
                // Arrange
                var controller = new SalesController(repository);
                var id = 75123;

                // Act
                var response = await controller.GetOrder(id) as ObjectResult;

                // Assert
                var value = response.Value as ISingleModelResponse<OrderHeaderViewModel>;

                Assert.False(value.DidError);
            }
        }

        [Fact]
        public async Task TestGetOrderNotFoundAsync()
        {
            using (var repository = RepositoryMocker.GetSalesRepository())
            {
                // Arrange
                var controller = new SalesController(repository);
                var id = 0;

                // Act
                var response = await controller.GetOrder(id) as ObjectResult;

                // Assert
                var value = response.Value as ISingleModelResponse<OrderHeaderViewModel>;

                Assert.False(value.DidError);
            }
        }
    }
}
 

Unit tests list:

NameDescription
TestGetOrdersAsyncRetrieve all orders with default page size and page number
TestGetOrdersSearchingBySalesOrderNumberAsyncRetrieve all orders match with sales order number "so72"
TestGetOrdersSearchingByCustomerNameAsyncRetrieve all orders match with customer name "hey"
TestGetOrdersSearchingBySalesOrderNumberAndCustomerNameAsyncRetrieve all orders match with sales order number "so72" and customer name "her"
TestGetOrderAsyncRetrieve one order by id 75123
TestGetOrderNotFoundAsyncRetrieve one order by not existing id

Now we can save all changes and rebuild our solution or we can run the unit tests from command line with this command: dotnet test

Running test from command line

These unit tests are about data reading at this moment, so we can run more than one time with no concern.

Refactor your Back-end code

As we can see at this point, we have many objects into OrderViewer project, as part of enterprice applications development it's not recommended to have all objects into UI project, we going to split our UI project follow these steps

  1. Right click on solution's name
  2. Add > New Project > .NET Core
  3. Set project's name to OrderViewer.Core
  4. OK

Now we add the entity framework core packages for new project.no

This is the structure for OrderViewer.Core project:

  • DataLayer
  • DataLayer.Contracts
  • DataLayer.DataContracts
  • DataLayer.Mapping
  • EntityLayer

Use the following image and refactor all classes to individual files:

Project refactoring structure

Take this task as a challenge for you, one you have refactor all your code, add reference to OrderViewer.Core project to OrderViewer project, save all changes and build your solution, you'll get errors on unit tests project, so add namespaces and reference in unit tests project, now save all changes and build your solution.

Extra challenge: add a product category list for this application, take a references from order list, anyway I'll add in some days but you can challenge your skills :)

If everything it's fine, we can run without errors our application.

Code Improvements

  1. Add pagination for order list component
  2. Add date pickers for order list component
  3. Add toaster notifications to UI
  4. Replace bootstrap with material design
  5. Add integration tests
  6. Another improvement according to your point of view, please let me know in the comments :)

Points of Interest

  • If we get a detail review on TypeScript code, we can see some similarity between C# code and TypeScript code, this is because TypeScript it's a typed language and we need to replicate the same structure from client side to set back-end results.
  • Angular 1.x was about Controllers, Angular 2 it's about Web Components

Related Links

 

LINK: https://www.codeproject.com/articles/1155666/creating-angular-application-with-asp-net-core-tem

Using MongoDB .NET Driver with .NET Core WebAPI

How to build step by step an ASP.NET Core WebAPI with latest MongoDB driver. The project supports all requests to MongoDB asynchronously. This is first part of building an Angular Notebook WebApp, presenting the backend.

Source could be also accessed from GitHub -> https://github.com/fpetru/WebApiMongoDB.

Update 14 Dec 2016

Following Marcello's comment, I have added a basic level of exception management.

Update 07 Dec 2016

  • I have extended the update function, making possible to modify full MongoDB documents at once, not just to some of the properties. There is a new section at the end of the article, describing this change.
  • I have updated the project to .NET Core 1.1 as well to MongoDB .NET Driver 2.4.

Update 25 Nov 2016

Trying to consume the service from Angular 2.0, see here the CodeProject article, I have ran into CORS problems: "No 'Access-Control-Allow-Origin' header is present on the requested resource."

How could this happened ?

Being different applications, running on seperate domains, all calls back to ASP.NET WebAPI site are effectively cross domain calls. With Angular 2, there is first a preflight request, before the actual request, which is an OPTIONS request. Doing this pre-check, we verify first that cross domain calls are allowed (CORS).

How I have enabled them ?

To do this I have made 2 changes:

  • Registered CORS functionality in ConfigureServices() of Startup.cs:
 public void ConfigureServices(IServiceCollection services) 
 {
      // Add service and create Policy with options 
      services.AddCors(options => { options.AddPolicy("CorsPolicy", 
                                      builder => builder.AllowAnyOrigin() 
                                                        .AllowAnyMethod() 
                                                        .AllowAnyHeader() 
                                                        .AllowCredentials()); 
                                  }); 
      // .... 

      services.AddMvc(); 
 }
  • And then enabled the policy globally to every request in the application by call app.useCors() in the Configure()method of Startup, before UseMVC.
 public void Configure(IApplicationBuilder app) 
 { 
    // ... 

    // global policy, if assigned here (it could be defined indvidually for each controller) 
    app.UseCors("CorsPolicy"); 

    // ... 

    // We define UseCors() BEFORE UseMvc, below just a partial call
    app.UseMvc(routes => {
 }

Does this have any impact to the other parts of solution ?

No, this refers just to this security policy. Even if this could be further and more selective applied, the rest of the article remains unchanged.

There is still one more update...

I have added a new article, which focuses more on client side using Angular 2, and how we effectively connect to this WebAPI. See here the article: Angular 2 with ASP.NET Core Web API – Build a simple Notebook app – Part 1.

Original article

Problem / solution format brings an easier understanding on how to build things, giving an immediate feedback. Starting from this idea, the blog post will present step by step how to build

a web application to store your ideas in an easy way, adding text notes, either from desktop or mobile, with few characteristics: run fast, save on the fly whatever you write, and be reasonably reliable and secure.

This blog post will implement just the backend, WebApi and the database access, in the most simple way. Next blog post, covers the front end, using Angular 2: Angular 2 with ASP.NET Core Web API – Build a simple Notebook app – Part 1

Topics covered

  • Technology stack
  • Configuration model
  • Options model
  • Dependency injection
  • MongoDb – Installation and configuration using MongoDB C# Driver v.2
  • Make a full ASP.NET WebApi project, connected async to MongoDB

Technology stack

The ASP.NET Core Web API has the big advantage that it can be used as HTTP service and it can be subscribed by any client application, ranging from desktop to mobiles, and also be installed on Windows, macOS or Linux.

MongoDB is a popular NoSQL database that makes a great backend for Web APIs. These lend themselves more to document store type, rather than to relational databases. This blog will present how to build a .NET Core Web API connected asynchronously to MongoDB, with full support for HTTP GET, PUT, POST, and DELETE.

To install

Here are all the things needed to be installed:

Creating the ASP.NET WebApi project

Launch Visual Studio and then access File > New Project > .Net Core > ASP.NET Core Web Application.

and then

Configuration

There are multiple file formats, supported out of the box for the configuration (JSON, XML, or INI). By default, the WebApi project template comes with JSON format enabled. Inside the setting file, order matters, and include complex structures. Here is an example with a 2 level settings structure for database connection.
AppSettings.json – update the file:

{
  "MongoConnection": {
    "ConnectionString": "mongodb://admin:abc123!@localhost",
    "Database": "NotesDb"
  },

  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

 

Dependency injection and Options model

Constructor injection is one of the most common approach to implementing Dependency Injection (DI), though not the only one. ASP.NET Core uses constructor injection in its solution, so we will also use it. ASP.NET Coreproject has a Startup.cs file, which configures the environment in which our application will run. The Startup.cs file also places services into ASP.NET Core’s Services layer, which is what enables dependency injection.

To map the custom database connection settings, we will add a new Settings class.

namespace NotebookAppApi.Model
{
    public class Settings
    {
        public string ConnectionString;
        public string Database;
    }

Here is how we modify Startup.cs to inject Settings in the Options accessor model:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.Configure<Settings>(options =>
    {
        options.ConnectionString = Configuration.GetSection("MongoConnection:ConnectionString").Value;
        options.Database = Configuration.GetSection("MongoConnection:Database").Value;
    });

Further in the project, settings will be access via IOptions interface:

IOptions<Settings>

 

MongoDB configuration

Once you have installed MongoDB, you would need to configure the access, as well as where the data is located.

To do this, create a file locally, named mongod.cfg. This will include setting path to the data folder for MongoDB server, as well as to the MongoDB log file. Please update these local paths, with your own settings:

systemLog:
  destination: file
  path: "C:\\tools\\mongodb\\db\\log\\mongo.log"
  logAppend: true
storage:
  dbPath: "C:\\tools\\mongodb\\db\\data"
security:
  authorization: enable

Run in command prompt next line. This will start the MongoDB server, pointing to the configuration file already created (in case the server is installed in a custom folder, please update first the command)

"C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --config C:\Dev\Data.Config\mongod.cf

Once the server is started (and you could see the details in the log file), run mongo.exe in command prompt. The next step is to add the administrator user to the database. Run mongodb with the full path (ex: “C:\Program Files\MongoDB\Server\3.2\bin\mongo.exe”).

and then copy paste the next code in the console:

use admin
db.createUser(
  {
	user: "admin",
	pwd: "abc123!",
	roles: [ { role: "root", db: "admin" } ]
  }
);
exit;

 

MongoDB .NET Driver

To connect to MongoDB, add via Nuget the package named MongoDB.Driver. This is the new official driver for .NET, fully supporting the ASP.NET Core applications.

Model

The model class (POCO) associated with each entry in the notebook is included below:

using System;
using MongoDB.Bson.Serialization.Attributes;

namespace NotebookAppApi.Model
{
    public class Note
    {
        [BsonId]
        public string Id { get; set; }
        public string Body { get; set; } = string.Empty;
        public DateTime UpdatedOn { get; set; } = DateTime.Now;
        public DateTime CreatedOn { get; set; } = DateTime.Now;
        public int UserId { get; set; } = 0;
    }
}

 

Defining the database context

In order to keep the functions for accessing the database in a distinct place, we will add a NoteContext class. This will use the Settings defined above.

public class NoteContext
{
    private readonly IMongoDatabase _database = null;

    public NoteContext(IOptions<Settings> settings)
    {
        var client = new MongoClient(settings.Value.ConnectionString);
        if (client != null)
            _database = client.GetDatabase(settings.Value.Database);
    }

    public IMongoCollection<Note> Notes
    {
        get
        {
            return _database.GetCollection<Note>("Note");
        }
    }
}

 

Adding the repository

Using a repository interface, we will implement the functions needed to manage the Notes. These will also use Dependency Injection (DI) to be easily access from the application (e.g. controller section):

public interface INoteRepository
{
    Task<IEnumerable<Note>> GetAllNotes();
    Task<Note> GetNote(string id);
    Task AddNote(Note item);
    Task<DeleteResult> RemoveNote(string id);
    Task<UpdateResult> UpdateNote(string id, string body);

    // demo interface - full document update
    Task<ReplaceOneResult> UpdateNoteDocument(string id, string body);

    // should be used with high cautious, only in relation with demo setup
    Task<DeleteResult> RemoveAllNotes();
}

The access to database will be asynchronous. We are using here the new driver, which offers a full async stack.

Just as an example: to get all the Notes, we make an async request:

public async Task<IEnumerable<Note>> GetAllNotes()
{
    return await _context.Notes.Find(_ => true).ToListAsync();
}

Here is the full implementation, for all basic CRUD operations:

public class NoteRepository : INoteRepository
{
    private readonly NoteContext _context = null;

    public NoteRepository(IOptions<Settings> settings)
    {
        _context = new NoteContext(settings);
    }

    public async Task<IEnumerable<Note>> GetAllNotes()
    {
        return await _context.Notes.Find(_ => true).ToListAsync();
    }

    public async Task<Note> GetNote(string id)
    {
        var filter = Builders<Note>.Filter.Eq("Id", id);
        return await _context.Notes
                             .Find(filter)
                             .FirstOrDefaultAsync();
    }

    public async Task AddNote(Note item)
    {
        await _context.Notes.InsertOneAsync(item);
    }

    public async Task<DeleteResult> RemoveNote(string id)
    {
        return await _context.Notes.DeleteOneAsync(
                     Builders<Note>.Filter.Eq("Id", id));
    }

    public async Task<UpdateResult> UpdateNote(string id, string body)
    {
        var filter = Builders<Note>.Filter.Eq(s => s.Id, id);
        var update = Builders<Note>.Update
                            .Set(s => s.Body, body)
                            .CurrentDate(s => s.UpdatedOn);
        return await _context.Notes.UpdateOneAsync(filter, update);
    }

    public async Task<ReplaceOneResult> UpdateNote(string id, Note item)
    {
        return await _context.Notes
                             .ReplaceOneAsync(n => n.Id.Equals(id)
                                                 , item
                                                 , new UpdateOptions { IsUpsert = true });
    }

    public async Task<DeleteResult> RemoveAllNotes()
    {
        return await _context.Notes.DeleteManyAsync(new BsonDocument());
    }    
}

In order to access NoteRepository using DI model, we add a new line in ConfigureServices

services.AddTransient<INoteRepository, NoteRepository>()

where:

  • Transient: Created each time.
  • Scoped: Created only once per request.
  • Singleton: Created the first time they are requested. Each subsequent request uses the instance that was created the first time.

Adding the main controller

First we present the main controller. It provides all the CRUD interfaces, available to external applications.

[Route("api/[controller]")]
public class NotesController : Controller
{
    private readonly INoteRepository _noteRepository;

    public NotesController(INoteRepository noteRepository)
    {
        _noteRepository = noteRepository;
    }

    // GET: notes/notes
    [HttpGet]
    public Task<string> Get()
    {
        return GetNoteInternal();
    }

    private async Task<string> GetNoteInternal()
    {
        var notes = await _noteRepository.GetAllNotes();
        return JsonConvert.SerializeObject(notes);
    }

    // GET api/notes/5
    [HttpGet("{id}")]
    public Task<string> Get(string id)
    {
        return GetNoteByIdInternal(id);
    }

    private async Task<string> GetNoteByIdInternal(string id)
    {
        var note = await _noteRepository.GetNote(id) ?? new Note();
        return JsonConvert.SerializeObject(note);
    }

    // POST api/notes
    [HttpPost]
    public void Post([FromBody]string value)
    {
        _noteRepository.AddNote(new Note() { Body = value, CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now });
    }

    // PUT api/notes/5
    [HttpPut("{id}")]
    public void Put(string id, [FromBody]string value)
    {
        _noteRepository.UpdateNote(id, value);
    }

    // DELETE api/notes/5
    public void Delete(string id)
    {
        _noteRepository.RemoveNote(id);
    }

Adding the admin controller

This will be a controller dedicated to administrative tasks (we use to initialize the database with some dummy data). In real projects, we should very cautiously use such interface. For development only and quick testing purpose, this approach may be convenient.

To use it, we will just add the url in the browser. Running the code below, the full setup will be automatically created (e.g. new database, new collection, sample records). We can use either http://localhost:5000/system/init or http://localhost:53617/system/init(when using IIS). We could even extend the idea, adding more commands. However, as mentioned above, these kind of scenarios should be used just for development, and be never deployed to a production environment.

[Route("api/[controller]")]

public class SystemController : Controller
{
    private readonly INoteRepository _noteRepository;

    public SystemController(INoteRepository noteRepository)
    {
        _noteRepository = noteRepository;
    }

    // Call an initialization - api/system/init
    [HttpGet("{setting}")]
    public string Get(string setting)
    {
        if (setting == "init")
        {
            _noteRepository.RemoveAllNotes();
            _noteRepository.AddNote(new Note() { Id = "1", Body = "Test note 1", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 1 });
            _noteRepository.AddNote(new Note() { Id = "2", Body = "Test note 2", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 1 });
            _noteRepository.AddNote(new Note() { Id = "3", Body = "Test note 3", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 2 });
            _noteRepository.AddNote(new Note() { Id = "4", Body = "Test note 4", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 2 });

            return "Done";
        }

        return "Unknown";
    }
}

 

Launch settings

In order to have a quick display of the values, once the project will run, please update the file launchSettings.json.

Here is the full file content, pointing by default to api/notes url.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:53617/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/notes",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "NotebookAppApi": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "http://localhost:5000/api/notes",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

 

Running the project

Before running the project, please make sure the MongoDB is running (either as an Windows Service, or via console application, as presented above).

Run first the initialization link:
http://localhost:53617/system/init

and then run the default application link
http://localhost:53617/api/notes

Use Robomongo

Using Robomongo we could check the actual entries inside the database. Connecting to the database, using the credentials, we could see all 4 records.

Fully update the MongoDB documents

Initially the sample project included only selective update of the properties. Using ReplaceOneAsync we could update the full document. Upsert creates the document, in case it doesn’t already exist.

public async Task<replaceoneresult> UpdateNote(string id, Note item)
{
     return await _context.Notes
                          .ReplaceOneAsync(n => n.Id.Equals(id)
                                            , item
                                            , new UpdateOptions { IsUpsert = true });
} 
</replaceoneresult>

Test the update

To be able to test the update, I have used Postman. It is an excellent tool to test APIs.

I have selected the command type POST, then entered the local URL, and added a new Header (Content-Type as application/json).

ASP.NET Core WebAPI Set-header

And then set the Body as raw and updated a dummy value.

ASP.NET Core WebAPI Make the request

Using RoboMongo we can see the value updated.

MongoDB .NET Driver Updated document in Robomongo

Exception management

Starting with C# 5.0 async and await were introduced into the language to simplify using the Task Parallel Library. We can simply use a try/catch block to catch an exception, like so:

public async Task<ienumerable<note>> GetAllNotes()
{
    try
    {
        return await _context.Notes.Find(_ => true).ToListAsync();
    }
    catch (Exception ex)
    {
        // log or manage the exception
        throw ex;
    }
}
</ienumerable<note>

In this way we handle a faulted task by asynchronously wait for it to complete, using await. This will rethrow the original stored exception. Initially I have used void as return. Changing the return type, the exception raised in the async method will get safely saved in the returning Task instance. When we await the faulty method, the exception saved in the Task will get rethrown with its full stack trace preserved.

public async Task AddNote(Note item)
{
    try
    {
        await _context.Notes.InsertOneAsync(item);
    }
    catch (Exception ex)
    {
        // log or manage the exception
        throw ex;
    }
}

LINK: https://www.codeproject.com/Articles/1151842/Using-MongoDB-NET-Driver-with-NET-Core-WebAPI