Single Project Full-Stack Angular 2

This is BETA software.

This article was written prior to the Release Candidate of Kendo UI for Angular. The ASP.NET Core Template Pack is still in development and supports only Angular version 2.0.0. To remain compatible with Angular 2.0.0 a beta version (0.10.0) of the Kendo UI for Angular Grid is required. Note that some demos may not work with current versions of the any software outlined below. We will update this document as new releases become available.

Whether you underestimated Angular 2 or believed in its complete success, it's time to learn what Angular 2 can do for you. Now that Angular 2 is out of beta, let's take a serious look at how to develop a full stack application using ASP.NET Core and Angular 2.

In this article we'll learn how to create the database, server-side and client-side bits of an application and we'll do it all in a single .NET project.

ASP.NET Core & ng2

In an ASP.NET Core project Angular 2 takes almost all responsibility for the view portion of the application. You won't see much razor (.cshtml) markup at all, in fact we won't work any at all in this tutorial. So "Why ASP.NET then?" one might ask. ASP.NET Core has a strong server-side offering, it's excellent for working with databases through Entity Framework (ORM), and it can serve our client-side application. In addition, we'll be using the luxury model editor that is Visual Studio 2015. While Visual Studio is optional, it's still the best developer experience for creating .NET applications.

Enough with the formalities, let's dig in and get some work done! Let's talk tooling and then get into some code.

ASP.NET Core new project template

It's important to note that, at the time of writing, every technology stack mentioned here is moving at a fast pace. We need to make sure that we're using the latest tooling or we could see errors when spinning up a new project.

Prerequisites

Before we get started, we need the following requirements:

File New Project

Creating a new .NET Core project that incorporates Angular 2 without a template can be a daunting task. For this reason, there are several ways of generating a new project that is ready to use. We'll be using the ASP.NET Core Template Pack, which adds the ASP.NET Core Angular 2 Starter Application template. If you're more familiar with the command line (CLI) the exact same template is available via Yeoman.

Installing the ASP.NET Core Template Pack will give you the ASP.NET Core Angular 2 Starter Application. The ASP.NET Core Angular 2 Starter Application provides the infrastructure needed for Angular 2. It includes Angular 2 dependencies, TypeScript definitions, and Webpack hooks for us.

mmkzwlj

Once the Template Pack is installed, create new project by clicking File > New Project and then select Web > ASP.NET Core Angular 2 Starter Application (.NET Core).

sx1s0wj

In the project we just created, we'll focus on the ClientApp and Controllers. The ClientApp folder in the application will contain all of our Angular 2 client-side code, while the Controllers folder contains all of our server-side Web APIs.

ientgep

We'll add a few dependencies along the way, but the template is a great start. Let's move on to the server-side and begin creating a database and API endpoint.

Creating an Database Context

To keep the example simple, we'll be using Entity Framework Core backed by an in-memory database. Entity Framework Core (EF) is an ORM that abstracts away most of our data access code so we can concentrate on building our app with .NET objects. The in-memory database will allow us to exercise EF without needing to setup a SQL database.

Install EF and the in-memory database by searching for Microsoft.EntityFrameworkCore.InMemory in NuGet. Installing Microsoft.EntityFrameworkCore.InMemory will also install EF if has not been added to your project already.

Next, let's add a simple model to represent the data in our application. Create a new folder named Models. Inside the Models add a new file Customer.cs and create a Customer class.

//Customer.cs
public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public double AccountLimit { get; set; }
}

Now that we have a Customer class, we'll need to create a database context for EF. The database context represents a session with the database and can be used to query and save instances of our entities. A database context is created by inheriting from DbContext and adding DbSet properties which generally correspond to database tables.

In the application root src, create a new folder named Data. In the Data folder, add a new class file named SalesContext. The SalesContext needs to inherit DbContext and requires a property Customers that returns a DbSet of Customer. We'll also need to override the base constructor and pass through the DbContextOptions options parameter.

public class SalesContext : DbContext
{
    public SalesContext(DbContextOptions<SalesContext> options)
        : base(options)
    {
    }

    public DbSet<Customer> Customers { get; set; }
}

To use the SalesContext in the application, we'll need to register it as a service to ASP.NET Core's dependency injection (DI) container. The DI container is configured in Startup.cs under the ConfigureServices method. We'll register SalesContext by calling AddDbContext with the type of SalesContext and pass in the options for our in-memory database, UseInMemoryDatabase.

// 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.AddDbContext<SalesContext>(opt => opt.UseInMemoryDatabase());
    services.AddMvc();
}

Next, we'll resolve the dependency and add seed data. Find the Configure method in Startup.cs and resolve SalesContext and add seed data to the Customers DbSet. Be sure to call SaveChanges to insert the new items.

// 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)
{
    var ctx = app.ApplicationServices.GetService<SalesContext>(); // Resolve SalesContext dependency
    ctx.Customers.Add(new Customer { Id = 0, AccountLimit = 10000.00, FirstName = "Rick", LastName = "Sanchez" });
    ctx.SaveChanges(); // Save the seed customer
    ...
}

Now we have a fully functional database that we can perform create, read, update and delete (CRUD) operations on. Next we'll need to build a set of API endpoints to handle these operations.

Creating an API endpoint

When we created a new project with the ASP.NET Core Angular 2 Starter Application a sample API endpoint was created for us in a controller named SampleDataController. We'll modify SampleDataController to expose data from Customers on our SalesContext. Let's begin by opening /Controllers/SampleDataController.cs and removing the existing example.

[Route("api/[controller]")]
public class SampleDataController : Controller
{
    // deleted demo code
}

Now we have an empty controller that we can add our own endpoints to. The controller will need access to the SalesContext so that we can perform CRUD operations against the database from endpoints we create. Resolving the SalesContext on the controller can be done by simply adding the proper constructor and the built-in DI container will handle the rest.

public class SampleDataController : Controller 
{
    private readonly SalesContext ctx;

    public SampleDataController(SalesContext context)
    {
        ctx = context;
    }
}

Next, add a new API endpoint that will fetch all of the Customers from our database so they can be displayed in a user interface (UI) that we will build later. We'll add a HttpGet attribute to the endpoint that specifies the name of the API endpoint [Action]. When we apply [Action], this tells ASP.NET Core to use the method name as the URI for the endpoint, in this case api/SampleData/Customers.

By default ASP.NET Core will return the data as JSON, so simply returning ctx.Customers will fetch and return all of the customers stored in our table.

/* /api/SampleData/Customers */
[HttpGet("[action]")] 
public IEnumerable<Customer> Customers() => ctx.Customers; // returns all customers as Json

Let's add an endpoint that fetches a single customer by id. On this endpoint we'll specify a the id parameter that will be used to filter the data from Customers and return the first instance of the customer with the corresponding id.

/* /api/SampleData/GetCustomer/{id} */
[HttpGet("{id}", Name="[action]")]
public Customer GetCustomer(int id) => ctx.Customers.First(c=> c.Id ==id);

Finally, we'll add an HttpPost method that inserts a new Customer into the database. We'll use the [FromBody] attribute to tell ASP.NET to bind the Customer from data received in the body of the request. We'll add this new customer to the database and return a URI pointing to the API location of the newly created Customer.

/* /api/SampleData/GetCustomer */
[HttpPost("[action]")]
public IActionResult AddCustomer([FromBody] Customer data)
{
    ctx.Add(data);
    ctx.SaveChanges();

    return CreatedAtRoute("GetCustomer", new { id = data.Id }, data);
    /* 
        Response 201
        /api/SampleData/GetCustomer/{id}
        { data as Json }
    */
}

With these endpoints we can create a UI to display customers and add new customers to the database. Next we'll build the client-side application utilizing Angular 2.

Consuming an API endpoint

Out-of-the-box Angular 2 doesn't offer any UI components. To display a table of data we will either need to write our own grid template, or we can utilize third-party components. Let's add Telerik Kendo UI for Angular 2 by Progress to our application and take advantage of the Kendo UI Grid Component.

Adding UI Components

To add Kendo UI for Angular 2, we'll need to create a free Telerik account. After creating an account, we'll need to enable the @progress npm scoped registry from the command line. Run the following command and enter your account details when prompted.

npm login --registry=https://registry.npm.telerik.com/ --scope=@progress

The @progress scoped registry only needs to be added once per machine and we won't need to add it again even for future projects.

Next, we add our Kendo UI controls to the project by opening package.json adding them to the dependencies node. We'll need kendo-angular-grid, kendo-data-query, and kendo-theme-default. Saving changes to package.json will trigger a restore in Visual Studio installing the packages to our project.

"dependencies": {
    "@progress/kendo-angular-grid": "0.10.0",
    "@progress/kendo-data-query": "*",
    "@telerik/kendo-theme-default": "*",

Now that we have Kendo UI dependencies added, we need to make them available in the client-side application. We'll need to import the Kendo UI Grid component in app.component.ts. We'll also add the Angular Forms modules to help us with CRUD operations with the grid.

import { GridModule } from '@progress/kendo-angular-grid';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

imports: [
    UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too.
    GridModule,
    FormsModule,
    ReactiveFormsModule,
    ...]

Finally, we'll need to configure webpack to use the Kendo UI theme. We'll add the CSS theme file we installed earlier to webpack.config.vendors.js by adding the path to the vendor array.

entry: {
    vendor: [
        ...,
        '@telerik/kendo-theme-default/dist/all.css'

We'll need to run webpack from the command line to recompile the static files in the vendors array re-building vendor.css which is used by the application.

webpack --config webpack.config.vendor.js

With all of the dependencies ready we can construct the application's UI.

Constructing a UI

For the client-side of the application, we'll build an Angular 2 component using Kendo UI for Angular 2. The ASP.NET Core Angular 2 Starter Application gave us a starting point with a component /ClientApp/app/components/fetchdata. Let's reuse the fetchdata component for our own needs. Open the component fetchdata.component.ts and clear out the template code leaving an empty.

import { Component } from '@angular/core';

@Component({
    selector: 'fetchdata',
    template: require('./fetchdata.component.html')
})

export class FetchDataComponent { }

To keep the scope of the example simple we'll be building all of our logic in one file. Some of the code could be abstracted out later to classes and services for a better separation of concerns.

We'll start by adding a class that acts as a data transfer object (DTO). Add a Customer class below the code for the component, include corresponding properties to the Customer class in the server-side code.

export class FetchDataComponent { }

export class Customer {
    constructor(
        public id: number,
        public firstName: string,
        public lastName: string,
        public accountLimit: number) { }
}

In this component, we'll be making HTTP requests and using observable data, let's add the modules needed to make this work.

import { Http, Headers, RequestOptions, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/switchMap';

In the FetchDataComponent, create customers property to hold the customers we will be displaying in our UI.

export class FetchDataComponent {
    customers: Array<Customer>;
}

Create a constructor that accepts an Http object as a parameter. The Http object will be supplied by Angular from the built in dependency injection container.

constructor(private http: Http) {
    
}

Next, add a getCustomers() function that returns an Observable of an array of Customer[]. We'll make an HTTP Get request to the endpoint of api/SampleData/Customers that we built using ASP.NET Core and map the response from the request.

getCustomers(): Observable<Customer[]> {
    return this.http.get('/api/SampleData/Customers')
               .map((res: Response) => res.json());
}

Complete the constructor by subscribing to getCustomers and applying the results to our customers property.

constructor(private http: Http) {
    this.getCustomers()
        .subscribe(data => this.customers = data);
}

The component is capable of fetching data from our API, but we need a template to display the data. Let's open fetchdata.component.html and create a UI for the component. Again, clear out any existing code that was generated for us.

In place of the generated code, we'll use a kendo-grid component. Setting the kendo-grid up for data binding is as easy as setting the [data] property to our component's customers value. Next, specify the kendo-grid-columns and set the title and field names that correspond to the Customer class.

<kendo-grid [data]="customers">
    <kendo-grid-column title="First Name" field="firstName"></kendo-grid-column>
    <kendo-grid-column title="Last Name" field="lastName"></kendo-grid-column>
    <kendo-grid-column title="Account Limit" field="accountLimit"></kendo-grid-column>
</kendo-grid>

ovgfbhl

The application is fully functional at this point. Run the application and watch the kendo-grid bind to data from the ASP.NET Core endpoint.

Writing data

So far we have built a fully functioning application from database, to Web API, to client-side UI. Let's continue improving the UI by adding the ability to add new items to the database by calling our API through our Angular 2 component.

In fetchdata.component.ts add a view property and seed it with a fully populated Customer. We'll submit the values of the view back to the server using an http post request.

export class FetchDataComponent {
    customers: Array<Customer>;
    view: Customer = new Customer(0, "Ed", "Charbeneau", 5000);
    ...
}

Create a saveCustomer function that returns an Observable of Customer. In the function we'll make a post to the AddCustomer endpoint on the server-side. The request needs the proper body and headers to satisfy the API endpoint.

saveCustomer(): Observable<Customer[]> {
    let body = JSON.stringify(this.view);
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.post('/api/SampleData/AddCustomer', body, options)
                    .map((res: Response) => res.json()); // ...and calling .json() on the response to return data
}

The saveCustomer function will allow us to post a new customer back to the server, we just need to subscribe to the observable. We'll create a new function addCustomer that subscribes to the observable. We'll use the switchMap operator to unsubscribe from any previous subscriptions when a value is created and subscribe to the newest response.

addCustomer(): void {
    this.saveCustomer().switchMap(x => this.getCustomers())
        .subscribe((response: Customer[]) => {
            this.customers = response;
        });
}

Finally, we'll update the UI to allow new users to be added with a form. Back in fetchdata.component.html we update the kendo-grid with a toolbar template. The kendo-grid-toolbar provides a template area for adding arbitrary HTML. Since we already have the view property on the component we can simply use [(ngModel)] for two-way data binding between HTML inputs and our data. Along with the inputs we'll add a submit button that triggers the addCustomer function.

<kendo-grid [data]="customers">
    <kendo-grid-toolbar>
        <label for="fn">First Name </label> <input id="fn" [(ngModel)]="view.firstName" />
        <label for="ln">Last Name </label> <input id="ln" [(ngModel)]="view.lastName" />
        <label for="al">Account Limit </label> <input id="al" [(ngModel)]="view.accountLimit" />

        <button (click)="addCustomer()" class="k-primary k-button k-button-icontext k-grid-add">Add new</button>
    </kendo-grid-toolbar>
    <kendo-grid-column title="First Name" field="firstName"></kendo-grid-column>
    <kendo-grid-column title="Last Name" field="lastName"></kendo-grid-column>
    <kendo-grid-column title="Account Limit" field="accountLimit"></kendo-grid-column>
</kendo-grid>

dxh337x

Conclusion

Going full stack with ASP.NET Core and Angular 2 might seem like an undertaking at first glance. However, the fact that the database, API, and UI layers could be covered in a single article tells a different story. Most of this process involved setup, and once an application is configured we can snap together components with relative ease.

What do you think of ASP.NET with Angular 2? Leave your comments below, let us know if you plan on making it your choice for a project in the future and how you might use the tools outlined here.

Related resources:

Comments