In this part of the series, you will learn how to accelerate development using C# tag helpers and Razor from ASP.NET Core MVC together with Angular 2. Yes - have your SPA and eat it too. Source now includes VS2015 and VS2017 versions.
Introduction
This is Part 2 of a series of articles. Part 1 covered a method of integrating ASP.NET Core and Angular 2.
Create an Angular Single Page Application or SPA and the terrific User Experience (Ux) can really impress users, and in turn, this is almost certain to impress your project sponsors and make happy product owners.
Why? Page navigation is snappy and fast since the first time you view a page, the view templates are cached, then subsequent calls to the same page only need to shout out for data, and not page + data. Even some of the data can be cached client side, and be re-used if you add some client side 'smarts' to your pages.
But suppose you have ASP.NET MVC developers on hand, and skills to create an Angular SPA are just out of reach. Then creating another MVC site seems easy. But look closely at what happens, your server will still be rendering and re-rendering both pages and data much of the time. Even with partial views and server side caching, this server side effort and extra traffic can make your site feel soggy and slow, users with limited mobile connectivity can suffer too as time for page views and their data costs increase.
But try to move to an Angular SPA, away from ASP.NET MVC, and your developers can lose some of their "super-powers" as they trade in familiar tools like Razor, or custom tag helpers and need to code some aspects of your application twice - once server side and again client side.
Previously, you could create data models, use automatically generated validation and crank out code quickly, but a typically attempt to push into the land of the SPA and you find you're now creating two sets of data models, creating data validation code client side in JavaScript or TypeScript, and then there's the server side data models and validation attributes server side in C# as well. Code become fragile, badly coupled, bugs and technical debt increases and the project deadlines seem further away.
As the big divide between server side and client side code (and coders) increases, your shiny ASP.NET Core MVC app is serving flat HTML and the only awesomeness left is serving data using Web API calls.
There is another alternative, use ASP.NET Core MVC partial views in place of flat HTML templates, and keep using the great ASP.NET Core features as well as those in Angular - you can get your SPA and eat it too (that is have an Angular SPA + Razor and Custom Tag Helpers).
Background
This series of articles are the end result of a number of web applications I have built over the last few years using Angular (1.x) and ASP.NET MVC 4.5x, now altered and adapted around ASP.NET Core MVC and Angular 2.
Caveat: I have not used this combination of Angular 2 and ASP.NET Core 1.x in production yet, though I have started projects for two clients using this code, it is still a work in progress, and may well take a few different turns along the way before it is complete. At the end however, I hope to share my findings and give you a head start into what I think is a better way to create an SPA.
Please don't look to these articles to tell you what is the best backend. I will not be proscribing anything particular there, instead I'll use a simple EF core + MS SQL backend. You could use CQRS, your favourite ORM, Raven, Mongo or whatever you want and as far as I can see, this will not matter here.
Instead, these articles will concentrate on how you can make Angular 2 code fit more easily into an ASP.NET Core MVC backend, how to automate a lot of the work, how to reduce bad coupling and help remove the issues of cut/paste repetition.
Part 1 of this series has a background on the basic way to get ASP.NET Core to serve smarter views to your Angular 2 SPA. We replace the standard flat HTML template views in Angular QuickStart ASP.NET Core with partial views served up from ASP.NET Core MVC and get something functionally very similar.
Here in Part 2, we will add a simple Web API service to the back end, and alter one of the Angular views to call an Angular service which in turn calls this new Web API service. This will introduce how tag helpers can reduce overheads of repetitive code and generate Angular markup at the same time.
Adding a Simple Web API Service
First, we add our Web API service; right click the A2SPA solution, at the root level, click Add, then New Folder:

Enter the new folder name Api:

Next right click this new folder, click Add, then New Item.

Now enter the word controller in the search box, from the results, choose Web API Controller Class, then rename the default name from ValuesController.cs to SampleDataController.cs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace A2SPA.Api
{
[Route("api/[controller]")]
public class SampleDataController : Controller
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
[HttpPost]
public void Post([FromBody]string value)
{
}
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
We'll leave this Web API controller with the default contents for now, as it will serve up a simple string array and ensure the services are running as planned without too much added complexity, yet.
Next, we'll add a folder called "services
" in the wwwroot/app folder. Again, just right click the parent "app" folder:

Right click the new folder, click Add, New Item, this time type typescript in the search box:

And again, click the Add button.
This new SampleData.services.ts will be a re-usable HTTP data service used by our Angular controller to fetch data from the Web API controller just created.
This template results in a blank file, so copy the following code into it:
import { Injectable } from '@angular/core'>;
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class SampleDataService {
private url: string = 'api/';
constructor(private http: Http) { }
getSampleData(): Observable<string[]> {
return this.http.get(this.url + 'sampleData')
.map((resp: Response) => resp.json())
.catch(this.handleError);
}
private handleError(error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
Next, we'll update the existing app.module.ts file to provide required dependencies, change it to the following:
import { NgModule, enableProdMode } from '@angular/core';
import { BrowserModule, Title } from '@angular/platform-browser';
import { routing, routedComponents } from './app.routing';
import { APP_BASE_HREF, Location } from '@angular/common'>;
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms'>;
import { HttpModule } from '@angular/http';
import { SampleDataService } from './services/sampleData.service';
import './rxjs-operators';
@NgModule({
imports: [BrowserModule, FormsModule, HttpModule, routing],
declarations: [AppComponent, routedComponents],
providers: [SampleDataService, Title, { provide: APP_BASE_HREF, useValue: '/' }],
bootstrap: [AppComponent]
})
export class AppModule { }
You'll notice that we've added a new import
:
import './rxjs-operators';
This will need another new typescript file. Just add a new Typescript file, in much the same way as we did a moment earlier, except this time, right click the wwwroot/app folder, then click Add, New Item, again type typescript in the search box, select Typescript File, change the name to rxjs-operators.ts.
This new file rxjs-operators.ts should be updated to contain the following code:
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
Now update our existing about.component.ts code so that on initialization, it will fetch data from our new Angular data service and in turn from the new Web API service:
import { Component, OnInit } from '@angular/core';
import { SampleDataService } from './services/sampleData.service';
@Component({
selector: 'my-about',
templateUrl: '/partial/aboutComponent'
})
export class AboutComponent implements OnInit {
testData: string[] = [];
errorMessage: string;
constructor(private sampleDataService: SampleDataService) { }
ngOnInit() {
this.sampleDataService.getSampleData()
.subscribe((data: string[]) => this.testData = data,
error => this.errorMessage = <any>error);
}
}
Lastly, we'll update the ASP.NET partial view/Angular template AboutComponent.cshtml so that it will consume our data.
Initially, AboutComponent.cshtml was:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>
Update this to the following:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Example of Angular 2 requesting data from ASP.Net Core.</p>
<p>Data:</p>
<table>
<tr *ngFor="let data of testData">
<td>{{ data }}</td>
</tr>
</table>
<div *ngIf="errorMessage != null">
<p>Error:</p>
<pre>{{ errorMessage }}</pre>
</div>
Rebuild your app, hit Ctrl-F5 and when you navigate to the About page, you should see our amazing new content. Not so amazing, but best start off small. The F12 browser debug window, console tab should show no errors:

And when you view the F12 debug window, and select the network tab, then you should be able to navigate to point of time where the "SampleData
" was fetched (if not visible, check the settings in the network debug tab and refresh the page to see it again), then when the SampleData
is selected on the left, you'll see the Request and (as below), Response data that was sent by our Web API data service to the browser, when requested by our new Angular Service.

Reading a small array of two string
s isn't likely to be very useful, in real life we have many different data types and we usually need to support more than reading data, we need to provide the usual "CRUD" operation (Create
, Read
, Update
, Delete
).
Support for CRUD operations will be added in a later part of this series of articles; we'll use tooling to automatically add these operations. Less manual coding using tooling will not only save time and reduce the chance of errors, but help remove the temptation from developers to cut and paste one service to create another as the number of services expands.
Still, if you’d like to get familiar with CRUD operations for Angular 2 data services in the meantime, you could refer to these Angular 2 tutorials:
In the next section, we'll be adding support for a few other data types to our Web API data service.
Supporting Multiple Data Types
Next, we'll expand our Web API controller to enable serving a wider variety of data types, demonstrate how this is usually handled for some basic operations and introduce another key way to accelerate development- tag helpers.
First to replace the simple array of string
s served up by our default Web API code in SampleDataController.cs, we'll create a new class, a View Model, called TestData
. Though small and simple to allow us to focus on the concepts involved, it will be closer to real-life data.
At the root of the A2SPA project, click Add, click New Folder, create a folder called ViewModels.
Right click the ViewModels folder, click Add, click Class, use the file name TestData.cs for the new class.
Update the content of TestData.cs to contain the following:
using System.ComponentModel.DataAnnotations;
namespace A2SPA.ViewModels
{
public class TestData
{
public string Username { get; set; }
[DataType(DataType.Currency)]
public decimal Currency { get; set; }
[Required, RegularExpression(@"([a-zA-Z0-9_\-\.]+)@
((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))
([a-zA-Z]{2,4}|[0-9]{1,3})", ErrorMessage = "Please enter a valid email address.")]
[EmailAddress]
[Display(Name = "EmailAddress", ShortName = "Email", Prompt = "Email Address")]
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.",
MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
}
}
Next, we'll add an equivalent data model TestData.ts into our Angular app, right click the wwwroot\app folder, click Add, click New Folder, call the folder models. Next, right click this new folder, click Add, click New Item, change the name to TestData.ts from the default.
Change the content of this new file to the following:
import { Component } from '@angular/core';
export class TestData {
username: string;
currency: number;
emailAddress: string;
password: string;
}
Now some of you will be already thinking hey this is repetitive, whatever happened to DRY "Don't Repeat Yourself"? True, for this initial cut, we're purposefully creating this small data model manually. Then, in the next parts of this series, we'll look refactoring this completely, and ultimately we'll automatically create all of our typescript data models and our typescript data services for Angular directly from our C# data model and Web API services.
This will mean we remove the bad coupling we'd often have, since changes to our data model, or data service methods won't cause a knock on effect, where we'd normally need to play catch up across our back end and front end.
Back to the code. We'll now update (just this once) our Angular service from the basic string array to instead cater for our newer, more complex data type, TestData
. Update your existing SampleData.service.ts to this:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { TestData } from './models/testData';
@Injectable()
export class SampleDataService {
private url: string = 'api/';
constructor(private http: Http) { }
getSampleData(): Observable<TestData> {
return this.http.get(this.url + 'sampleData')
.map((resp: Response) => resp.json())
.catch(this.handleError);
}
private handleError(error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
Next, we need to update our Angular component about.component.ts to handle the new TestData
data type instead of the earlier, simpler string
array.
import { Component, OnInit } from '@angular/core';
import { SampleDataService } from './services/sampleData.service';
import { TestData } from './models/testData';
@Component({
selector: 'my-about',
templateUrl: '/partial/aboutComponent'
})
export class AboutComponent implements OnInit {
testData: TestData = [];
errorMessage: string;
constructor(private sampleDataService: SampleDataService) { }
ngOnInit() {
this.sampleDataService.getSampleData()
.subscribe((data: TestData) => this.testData = data,
error => this.errorMessage = <any>error);
}
}
Finally, so that we can view data now it is in shape of our new data type, TestData
, we need to we'll alter our About view AboutComponent.cshtml
to this:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Example of Angular 2 requesting data from ASP.Net Core.</p>
<p>Data:</p>
<div *ngIf="testData != null">
<table>
<tr>
<th> Username: </th>
<td> {{ testData.username }} </td>
</tr>
<tr>
<th> Currency: </th>
<td> {{ testData.currency }} </td>
</tr>
<tr>
<th> Email Address: </th>
<td> {{ testData.emailAddress }} </td>
</tr>
<tr>
<th> Password: </th>
<td> {{ testData.password }} </td>
</tr>
</table>
</div>
<div *ngIf="errorMessage != null">
<p>Error:</p>
<pre>{{ errorMessage }}</pre>
</div>
There's a couple of small changes here to cater for the flat data; previously the *ngFor
loop handled empty data sets, so long as the data was initialized as an empty array. Here, we need to prevent using data that is not initialized as we've left the data as null
into it gets populated by our service. Alternately, we could have initialized the empty data to ensure each property existed, and removed the *ngIf
null
detection, as the properties would exist and thereby not throw errors.
We could have used an arrays of our TestData
object, but I've left this out of this section, in order that the purpose behind next steps are clearer and more obvious.
The last step in this section will be to alter our Web API controller to serve TestData
shaped data instead of a simple string
array.
using Microsoft.AspNetCore.Mvc;
using A2SPA.ViewModels;
namespace A2SPA.Api
{
[Route("api/[controller]")]
public class SampleDataController : Controller
{
[HttpGet]
public TestData Get()
{
var testData = new TestData
{
Username="BillBloggs",
EmailAddress = "bill.bloggs@example.com",
Password= "P@55word",
Currency = 123.45M
};
return testData;
}
[HttpPost]
public void Post([FromBody]TestData value)
{
}
[HttpPut("{id}")]
public void Put(int id, [FromBody]TestData value)
{
}
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
Again this is simplified, and no database yet, as we're going to focus on the current bigger picture issues.
So let's build this, ensure it still works. Rebuild and hit Ctrl-F5:

Normally, we'd not be displaying a password like this, and if we were entering a password it would be in a text box, so next we're going to see what our view would look like to display and enter properties from our TestData
object.
We'll first do this the way most people do, by hand, and then we'll redo it the easy way - using tag helpers. And this will be where the fun really begins!
Angular Views - Data Entry and Data Display
When setting up an HTML form, irrespective of it being your design or not, you have something you want to look good to the end user but underneath something you will hook up to your front end code, and in case of a SPA, through a RESTful service to your backend code.
The Angular Hello World "Party Trick"
Using our simple sample data model as an example, we'll set up one column to display data, and another for data entry for each of our four data types. This will be something like the typical Angular "hello world
" demo, where you'll be able to type data into an input form field and then as you type, see the text changes in another area of the page. This "party trick" is always impressive, and can usually get quite a bit of interest and excitement from management and end users, who have never seen 2 way data binding in action.
First, we'll create the required code by hand to see what a basic version of the backend code would look like. In this first step, we'll not use Razor or Tag Helpers, instead stick to plain HTML, CSS and Angular markup.
Return to your AboutComponent.cshtml page and change it to this:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Examples of Angular 2 data served by ASP.Net Core Web API:</p>
<form>
<div *ngIf="testData != null">
<div class="row">
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">Data Entry</div>
<div class="panel-body">
<div class="form-group">
<label for="testDataUsername">Username</label>
<input type="text" id="testDataUsername" name="testDataUsername"
class="form-control" placeholder="Username"
[(ngModel)]="testData.username">
</div>
<div class="form-group">
<label for="testDataCurrency">Amount (in dollars)</label>
<div class="input-group">
<div class="input-group-addon">$</div>
<input type="number" id="testDataCurrency"
name="testDataCurrency"
class="form-control" placeholder="Amount"
[(ngModel)]="testData.currency">
</div>
</div>
<div class="form-group">
<label for="testDataemailaddress">Email address</label>
<input type="email" id="testDataemailaddress"
name="testDataemailaddress"
class="form-control" placeholder="Email Address"
[(ngModel)]="testData.emailAddress">
</div>
<div class="form-group">
<label for="testDatapassword">Password</label>
<input type="password" id="testDatapassword"
name="testDatapassword"
class="form-control" placeholder="Password"
[(ngModel)]="testData.password">
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">Data Display</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">Username</label>
<p class="form-control-static">{{ testData.username }}</p>
</div>
<div class="form-group">
<label class="control-label">Amount (in dollars)</label>
<p class="form-control-static">
{{ testData.currency | currency:'USD':true:'1.2-2' }}
</p>
</div>
<div class="form-group">
<label class="control-label">Email address</label>
<p class="form-control-static">{{ testData.emailAddress }}</p>
</div>
<div class="form-group">
<label class="control-label">Password</label>
<p class="form-control-static">{{ testData.password }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
To support the updated validation, edit the custom styles in the file /wwwroot/css/site.css by adding these to the end of the file:
/* validation */
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
Save and hit Ctrl-F5 to build and view the changes; you should see a new About page looking like this:

When this page is loaded, you'll see the data delivered from the service, on the left and right halves of the page, but as you alter the data on the left hand side, the right hand side will follow the changes.
Of course in a 'real' application, we'd never show the password in plain text, let alone store it in plain text, as we have here, but this is a sample app.
Manually Adding Some Validation
We're not adding validation covering everything everywhere, but once again, we'll add the required HTML, CSS and Angular markup manually, to see what some basic validation would look like, and get a feel for how these elements work.
Edit the AboutComponent.cshtml page and change it to this:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Examples of Angular 2 data served by ASP.Net Core Web API:</p>
<form #testForm="ngForm">
<div *ngIf="testData != null">
<div class="row">
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">Data Entry</div>
<div class="panel-body">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username"
required minlength="4" maxlength="24"
class="form-control" placeholder="Username"
[(ngModel)]="testData.username" #name="ngModel">
<div *ngIf="name.errors && (name.dirty || name.touched)"
class="alert alert-danger">
<div [hidden]="!name.errors.required">
Name is required
</div>
<div [hidden]="!name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div [hidden]="!name.errors.maxlength">
Name cannot be more than 24 characters long.
</div>
</div>
</div>
<div class="form-group">
<label for="currency">Payment Amount (in dollars)</label>
<div class="input-group">
<div class="input-group-addon">$</div>
<input type="number" id="currency" name="currency"
required
class="form-control" placeholder="Amount"
[(ngModel)]="testData.currency" #currency="ngModel">
</div>
<div *ngIf="currency.errors &&
(currency.dirty || currency.touched)"
class="alert alert-danger">
<div [hidden]="!currency.errors.required">
Payment Amount is required
</div>
</div>
</div>
<div class="form-group">
<label for="emailAddress">Email address</label>
<input type="email" id="emailAddress" name="emailAddress"
required minlength="6" maxlength="80"
pattern="([a-zA-Z0-9_\-\.]+)@@
((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|
(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})"
class="form-control" placeholder="Email Address"
[(ngModel)]="testData.emailAddress" #email="ngModel">
<div *ngIf="email.errors && (email.dirty || email.touched)"
class="alert alert-danger">
<div [hidden]="!email.errors.required">
Email Address is required
</div>
<div [hidden]="!email.errors.pattern">
Email Address is invalid
</div>
<div [hidden]="!email.errors.minlength">
Email Address must be at least 6 characters long.
</div>
<div [hidden]="!email.errors.maxlength">
Email Address cannot be more than 80 characters long.
</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password"
required minlength="8" maxlength="16"
class="form-control" placeholder="Password"
[(ngModel)]="testData.password">
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">Data Display</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">Username</label>
<p class="form-control-static">{{ testData.username }}</p>
</div>
<div class="form-group">
<label class="control-label">Payment Amount (in dollars)</label>
<p class="form-control-static">
{{ testData.currency | currency:'USD':true:'1.2-2' }}
</p>
</div>
<div class="form-group">
<label class="control-label">Email address</label>
<p class="form-control-static">{{ testData.emailAddress }}</p>
</div>
<div class="form-group">
<label class="control-label">Password</label>
<p class="form-control-static">{{ testData.password }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<div *ngIf="errorMessage != null">
<p>Error:</p>
<pre>{{ errorMessage }}</pre>
</div>
/* validation */
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
Save the files, and again hit Ctrl-F5 to rebuild and launch your browser, navigate to the About page and this time, you should see much the same as before, but now including a few basic items of validation:

Try changing the username to less than 4 characters and as you make the field invalid, you'll see a validation error message:

Navigate away from the form field and you'll clearly see the error style:

Instead of reinventing the wheel, and covering how to do validation, please see the excellent Angular 2 tutorial site by the Angular 2 dev team here and here.
What Could Possibly Go Wrong Here?
Now that we have a little more to demonstrate and test, at this point, we're going to pause and cover some of the issues that may arise when trying to use ASP.NET Core views, Razor and Angular 2 together.
Escaping @ Symbols
The @
symbol needs to be 'escaped', as it has a loaded meaning in Razor. In the above code, I've included the already escaped @
symbol as @@
, which gets translated to a single @
. Code (from above) for the email entry and email regex validation is a good example of this:
<input type="email" id="emailAddress" name="emailAddress"
required minlength="6" maxlength="80"
pattern="([a-zA-Z0-9_\-\.]+)@@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|
(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})"
class="form-control" placeholder="Email Address"
[(ngModel)]="testData.emailAddress" #email="ngModel">
If escaped as @@
our source from Visual Studio looks like this:

If left as a single @
, it will trigger Razor syntax parsing and look like this:

All we need to do then is change any @
to @@
and similarly, if there happens to be a case of the original having two @
symbols, simply double it, 2 becomes 4, and then when passed through the Razor parser, each set of two @
symbols returns back to a single @
symbol.
Missing and Mismatched HTML Tags
The next issue is not so much an issue of ASP.NET Core, or Razor, but with Angular which happens to be particularly sensitive to missing HTML tags and mismatched HTML.
To see what this looks like, we'll purposefully remove a closing div
tag, shown highlighted on line 33 below. With the </div>
left in place:

Now with this single </div>
removed, save and hit Ctrl-F5, the page starts loading but the loader message does not go away. Hit F12 and look at the console and you'll see:

Expanding the above error in the console will not usually give you a clear idea of what is happening, so perhaps the best advice I can give is, if you see an error similar to the above, then return to the HTML template, and look through the markup (and though somewhat artificial) look at line 9 and you'll see an indication of a typical error you need to look for, the green squiggly line:

Hover your mouse over the green line and you'll see more detail:

In practice, you'll likely still need to do some work to find precisely where the DIV
mismatch occurs, as the mismatch warning is not necessarily on the tag that was missing the closing div
, but on the first div
tag directly affected by the missing closing tag.
Angular Views Using ASP.NET Core Tag Helpers
What are Tag Helpers?
A quick aside, for those more familiar with Angular and less familiar with ASP.NET Core. Tag helpers are similar in some respect to Angular directives; they are added to a page and at a glance, they look like a custom HTML tag.
Tag helpers are processed on the server by ASP.NET Core. They come in two main flavours, (i) built in tag helpers, and (ii) custom tag helpers - which you can create and customise yourself. They might best be explained by an example.
This example is from the documentation here, where we start with a data model. Update CSHTML markup using built in tag helpers:

And then this HTML is produced by ASP.NET Core and sent to the browser:

Tag Helpers (built in and fixed) feel like HMTL to a developers or designers. Because they work like ordinary HTML, people more familiar with HTML and less familiar with server side Razor syntax or client side code can work on your pages without getting lost in the code around the tag helper markup.
Tag Helpers can also support standard style sheet classes and, unlike Angular directives, provide extensive intellisense (code hints and lookup) within Visual Studio to assist developer and designer alike.
Lastly, and in my opinion, the biggest advantage is reduction of repeated code, keeping the code DRY - the Don't Repeat Yourself principle, to save time and money as we'll have less code to write and for the longer term, generate less code to maintain.
By now, you have figured where we're heading, we'll be custom tag helpers. However we'll take it to another level, as we'll use our tag helpers to create as much of our Angular markup at the same time, as possible.
Instead of multiple copies of many very similar HTML labels, input tags, and styling across a site you can have your tag helper backend code all in one place while the smaller tag helper markup holds the vital information about each instance.
To read a little more on the topic of tag helpers, please see the ASP.NET Core docs here, or Dave Paquette's article here.
We're going to make two custom tag helpers, one for data entry and the other for displaying data. These tag helpers will dynamically create all of the labels, basic styling and the form fields for our page as we can.
Data Validation?
When developers switch from conventional ASP.NET using razor syntax and tag helpers to build a SPA (or single page application), one of the biggest losses to the team can be the added burden of generating client-side data validation.
Server side validation is easily automated and generated dynamically using the metadata from the attributes decorating the data model. Some people have used code generation, to create the client side JavaScript required, other create data validation services from the metadata, validation data traffic can be an issue and we tend to still create too much bad coupling.
The alternative I am proposing here is that we use our custom tag helpers to create the bulk of our client side validation and that we still use server-side data validation as before. If there are complex database lookups required for some client side data I'd still recommend a hand created validation service, however the bulk of our work can be automated.
First a little housekeeping, we'll update the Microsoft.AspNetCore.MVC
assembly and Microsoft.AspNetCore.SpaServices
assembly using NuGet:

Among other things, the recent update of the ASP.NET Core MVC corrects potential security issues, see here for details.
Displaying Data With a Custom Tag Helper
We'll begin with the simpler of our two tag helpers, used to display data.
When designing the tag helper, you will find it helpful to begin with some idea of what you want to achieve. That's not to say you can't change it later, but if you have a good example of the layout, the styles used and tags required, then you can avoid change later.
If you're building a larger website and have just yourself or a team of developers, all waiting on a designer or agency to supply the final look-and feel, then you can still start, and add this later, although it will always be easier to have these final designs as early as possible.
For our code, we'll start with an example of some of the HTML that we'd like to reproduce:
<div class="form-group">
<label class="control-label">Username</label>
<p class="form-control-static">{{ testData.username }}</p>
</div>
<div class="form-group">
<label class="control-label">Payment Amount (in dollars)</label>
<p class="form-control-static">
{{ testData.currency | currency:'USD':true:'1.2-2' }}
</p>
</div>
Next, we'll choose a suitable tag to use for our tag helper that will not conflict with other regular HTML, tag helper or Angular directives we'll want to use. I generally pick something short but descriptive, so for the sake of this, I'm using <tag-dd>
from "tag
" as it stands out (but this could be related to the project name), and "dd
" for data display (can be anything memorable, say "cart
" or "sc
" for shopping-cart).
Create a folder at the root level of the application called say "Helpers", and then hit Shift-Alt-C (or create a new C# class file), call it TagDdTagHelper.cs.
As low level details on creating a custom tag helper are already well covered in a number of other places, consistent with this series of articles, we will not be going into very low level details.
If you need more, these links might help: ASP.NET Core documentation here, Dave Paquette's post here or the ASP.NET monster's videos covering tag helpers, over at MSDN Channel 9 here.
Before we go further, right click the project root and using your NuGet Package manager, add the package Humanizer.xproj:

NOTE: Do not add the ordinary Humanizer package, the non .xproj version of Humanizer will install but not build.

While here in NuGet, we'll pick up those updates:

And we'll add some tooling to Razor (we'll use this later), just browse for the package "Microsoft.AspNet.Tooling.Razor
" and then click "Install" button:

Now update the new TagDdTagHelper.cs class to contain the following code:
using Humanizer;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace A2SPA.Helpers
{
[HtmlTargetElement("tag-dd")]
public class TagDdTagHelper : TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var labelTag = new TagBuilder("label");
labelTag.InnerHtml.Append(For.Metadata.Description);
labelTag.AddCssClass("control-label");
var pTag = new TagBuilder("p");
pTag.AddCssClass("form-control-static");
pTag.InnerHtml.Append("{{ testData." + For.Name.Camelize() + "}}");
output.TagName = "div";
output.Attributes.Add("class", "form-group");
output.Content.AppendHtml(labelTag);
output.Content.AppendHtml(pTag);
}
}
}
Edit your AboutComponent.cshtml file, add this to the very top of the markup:
addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper "*,A2SPA"
@model A2SPA.ViewModels.TestData
And then scroll down to the existing markup that displays the username. Initially, we'll add our new tag-helper tag beside the existing markup, you'll notice that we can see intellisense at work in the image below, in this instance, hinting at the properties available to us:

Choose "username
", then hit Enter.
Sadly, the current tooling emits a lowercased property name, so for now, alter the first character to uppercase, so that it reads for="Username"
instead of for="username"
.
The code segment should now read:
...<div class="panel-heading">Data Display</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">Username</label>
<p class="form-control-static">{{ testData.username }}</p>
</div>
<tag-dd for="Username"></tag-dd>
<div class="form-group">...
Rebuild and it Ctrl-F5 to view, you should now see username twice:

Note: If you get an error here, double check you have not missed the manual upper case needed on the model property name.
Well we see the username but no label. The reason is our tag helper is relying on the data model now, instead of hand-coded text.
The fix is simple, we'll update the metadata in the data model, /ViewModels/TestData.cs to this:
using System.ComponentModel.DataAnnotations;
namespace A2SPA.ViewModels
{
public class TestData
{
public string Username { get; set; }
[DataType(DataType.Currency)]
public decimal Currency { get; set; }
To add the description meta data, like this:
using System.ComponentModel.DataAnnotations;
namespace A2SPA.ViewModels
{
public class TestData
{
[Display(Description = "Username")]
public string Username { get; set; }
[DataType(DataType.Currency)]
public decimal Currency { get; set; }
Rebuild again and it Ctrl-F5 to view, this time you should now see both username label and data twice:

And in the F12 debug view, these should have identical markup.

In the inspector, we see the final DOM after Angular has completed rendering, if we look over in the network tab, we'll see there is only a slight difference, our tag helper has generated a little less whitespace:

Before we add our other datatypes, time for some refactoring.
Improving Our Tag Helper
Currently, the first cut of the data display tag helper is functional, but quite crude. Ideally, we want to automate everything we can, and where possible, use convention over configuration.
What's convention over configuration? Time saving + automation. Here's how it works.
Look at our TagDaTagHelper.cs class where we create the name of the Angular object we're binding to:
pTag.InnerHtml.Append("{{ testData." + For.Name.Camelize() + "}}");
How does this work? When we pass in the For
attribute value, "Username
" it's treated as a property, recall the tag helper attribute, from our TagDaTagHelper.cs code:
/// <summary>
/// Name of data property
/// </summary>
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
when we enter this in the AboutComponent.cshtml view, it looks like a string
:
<tag-dd for="Username"></tag-dd>
But when we reference it in code, it's treated as a ModelExpession
, so we take it and get the .Name
property.
Next we use the Humanizer package's "Camelize
" method to take the name of the C# data model property ("Username
", where the convention is Pascal Case) and make it into a suitable Angular property name ("username
", where the convention is Camel Case).
The next part of the string hardcoded for now, adds "testData.
" to our camelized property name. The reason is that we have an object that will exist in our client called testData
.
Configuration would have us add another attribute to the custom tag, while convention says "save time, assume it matches, and configure only if convention doesn't fit".
So let's modify our tag helper, now the developer(s) all assuming a rule or "convention" exists that says name objects and properties consistently between Angular data models and C# data models, and use a tool such as Humanizer to make Pascal Cased wording into Camel Cased wording.
var className = For.Metadata.ContainerType.Name;
pTag.InnerHtml.Append("{{" + className.Camelize() + "." + For.Name.Camelize() + "}}");
It so happens we can get the parent class name by using For.MetaData.ContainerType.Name
and next, we'll extract the property name as well:
var className = For.Metadata.ContainerType.Name;
var propertyName = For.Name;
pTag.InnerHtml.Append("{{" + className.Camelize() + "." + propertyName.Camelize() + "}}");
Look at the AboutComponent.cshtml view and you'll see there's a lot of places using the same className.propertyName
style Angular object, so let’s create a method to do this for us that we'll use in our second tag helper too.
using Humanizer;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace A2SPA.Helpers
{
[HtmlTargetElement("tag-dd")]
public class TagDdTagHelper : TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var labelTag = new TagBuilder("label");
labelTag.InnerHtml.Append(For.Metadata.Description);
labelTag.AddCssClass("control-label");
var pTag = new TagBuilder("p");
pTag.AddCssClass("form-control-static");
pTag.InnerHtml.Append("{{" + CamelizedName(For) + "}}");
output.TagName = "div";
output.Attributes.Add("class", "form-group");
output.Content.AppendHtml(labelTag);
output.Content.AppendHtml(pTag);
}
private static string CamelizedName(ModelExpression modelExpression)
{
var className = modelExpression.Metadata.ContainerType.Name;
var propertyName = modelExpression.Name;
return className.Camelize() + "." + propertyName.Camelize();
}
}
}
For cleanliness, I like to move helper methods like this into a separate class file, or at least into a new class which makes it easier for people to find any shared methods. Create a new class called VariableNames.cs, in the Helpers folder:

Next, move the new method "CamelizedName
" into the new class. Last of all, we'll change our new CamelisedName
method into an extension method; alter the method to public
, change the VariableNames
class to static
, and alter the method signature, adding "this
". We need to add a couple of dependencies, so that the end result of VariableNames.cs is this:
using Humanizer;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace A2SPA.Helpers
{
public static class VariableNames
{
public static string CamelizedName(this ModelExpression modelExpression)
{
var className = modelExpression.Metadata.ContainerType.Name;
var propertyName = modelExpression.Name;
return className.Camelize() + "." + propertyName.Camelize();
}
}
}
And our tag helper TagDaTagHelper.cs is now this:
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace A2SPA.Helpers
{
[HtmlTargetElement("tag-dd")]
public class TagDdTagHelper : TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var labelTag = new TagBuilder("label");
labelTag.InnerHtml.Append(For.Metadata.Description);
labelTag.AddCssClass("control-label");
var pTag = new TagBuilder("p");
pTag.AddCssClass("form-control-static");
pTag.InnerHtml.Append("{{" + For.CamelizedName() + "}}");
output.TagName = "div";
output.Attributes.Add("class", "form-group");
output.Content.AppendHtml(labelTag);
output.Content.AppendHtml(pTag);
}
}
}
BTW, we could clean up the code around this string
concatentation:
pTag.InnerHtml.Append("{{" + For.CamelizedName() + "}}");
But this replacement with string.Format
means we'd have to "escape" the "{
" symbol and "}
" symbol, and end up with somewhat unreadable code:
pTag.InnerHtml.Append(string.Format("{{{{ {0} }}}}", For.CamelizedName()));
Finishing the First Cut of Our Data Display Tag Helper
We can now update our view AboutComponent.cshtml adding our new tag-helper tags in place of the existing markup, but leaving the currency value in place for now, as we need to add custom pipes.
...
<div class="panel-body">
<tag-dd for="Username"></tag-dd>
<div class="form-group">
<label class="control-label">Payment Amount (in dollars)</label>
<p class="form-control-static">
{{ testData.currency | currency:'USD':true:'1.2-2' }}
</p>
</div>
<tag-dd For="Currency"></tag-dd>
<tag-dd For="EmailAddress"></tag-dd>
<tag-dd For="Password"></tag-dd>
</div>
...
Again, don't forget to manually uppercase the property names. (Hopefully, this will be fixed in a later release of the ASP.NET Core MVC assembly).
Also needed, a couple of further updates to our view model, TestData.cs, adding descriptions to the other properties so that our labels are populated automatically:
using System.ComponentModel.DataAnnotations;
namespace A2SPA.ViewModels
{
public class TestData
{
[Display(Description = "Username")]
public string Username { get; set; }
[Display(Description = "Payment Amount (in dollars)")]
[DataType(DataType.Currency)]
public decimal Currency { get; set; }
[Required, RegularExpression(@"([a-zA-Z0-9_\-\.]+)@
((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))
([a-zA-Z]{2,4}|[0-9]{1,3})", ErrorMessage = "Please enter a valid email address.")]
[EmailAddress]
[Display(Description = "Username", Name = "EmailAddress",
ShortName = "Email", Prompt = "Email Address")]
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.",
MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Description = "Password", Name = "Password")]
public string Password { get; set; }
}
}
Once again, build and hit Ctrl-F5 to re-display:

And you can see the last part remaining in this first tag helper:
<div class="form-group">
<label class="control-label">Payment Amount (in dollars)</label>
<p class="form-control-static">
{{ testData.currency | currency:'USD':true:'1.2-2' }}
</p>
We need a way to add special formatting. Here you have a choice, you can fix it, either
- adding the pipe text into the tag helper so that all instances of currency use it, or
- dynamically add the country specific details based on the client/browser's language, or the server language settings, or
- add an optional attribute that you would need to add whenever you use currency, but this means you could at least customize different occurrences in your code, or
- a combination of the above, perhaps (i) as a default do all instances, and (iii) allow customization as well.
We'll be using (iii), adding an optional pipe attribute.
Add this optional attribute into our TagDaTagHelper.cs code, alongside the "For
" attribute:
[HtmlAttributeName("pipe")]
public string Pipe { get; set; } = null;
Then using this code:
var pipe = string.IsNullOrEmpty(Pipe) ? string.Empty : Pipe;
and:
pTag.InnerHtml.Append("{{" + For.CamelizedName() + pipe + "}}");
Here is the final data display tag helper code:
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace A2SPA.Helpers
{
[HtmlTargetElement("tag-dd")]
public class TagDdTagHelper : TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
[HtmlAttributeName("pipe")]
public string Pipe { get; set; } = null;
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var pipe = string.IsNullOrEmpty(Pipe) ? string.Empty : Pipe;
var labelTag = new TagBuilder("label");
labelTag.InnerHtml.Append(For.Metadata.Description);
labelTag.AddCssClass("control-label");
var pTag = new TagBuilder("p");
pTag.AddCssClass("form-control-static");
pTag.InnerHtml.Append("{{" + For.CamelizedName() + pipe + "}}");
output.TagName = "div";
output.Attributes.Add("class", "form-group");
output.Content.AppendHtml(labelTag);
output.Content.AppendHtml(pTag);
}
}
}
Then we need to update our view to this:
<tag-dd For="Currency" pipe="|
currency:'USD':true:'1.2-2'"></tag-dd>
Which results in this, when we build and hit Ctrl-F5 again:

Here then is an excerpt from the final AboutComponent.cshtml code:
…
<div class="panel-body">
<tag-dd for="Username"></tag-dd>
<tag-dd For="Currency" pipe="| currency:'USD':true:'1.2-2'"></tag-dd>
<tag-dd For="EmailAddress"></tag-dd>
<tag-dd For="Password"></tag-dd>
</div>
…
Obviously, you can extend this simple version of the tag helper code to handle other data types, allow custom classes, formatting, different colors or whatever you wish.
As an example, our password would never be shown in plain text like this, so for the sake of this simple demo, we'll modify our tag helper to hide the password (of course, you'd never send it from your Web API get method and you would have salted and hashed the password, rather than the simple plain text example we have here).
Look at the data model, notice that despite being a string
property that we've decorated our view model with a specific data type to further hint at the purpose of the data.
[DataType(DataType.Password)]
[Display(Description = "Password", Name = "Password")]
public string Password { get; set; }
So if we check on this data type in our tag helper, we can change what we emit; instead of creating an Angular data binding expression, we simply create a short string. This change:
var dataBindExpression = ((DefaultModelMetadata)For.Metadata).DataTypeName == "Password"
? "******"
: "{{" + For.CamelizedName() + pipe + "}}";
pTag.InnerHtml.Append(dataBindExpression);
Will render asterisks in place of the password.
Reality check: Don't do this in your production code … please! Again, this is just a simple to understand example!

or handle optional prefixes (instead of assuming that we're always using the class name of the view model, but whatever direction you take, look for patterns in your client side code that you can reduce into a tag helper, try to use data types, data model metadata and anything else you can to automate code creation and reduce special cases.
Your code will be simpler, and allow you the flexibility to change the code in one place, the next time someone asks for a change.
The source for this is on Github here, or available for download here.
In the next part of the series, we'll be creating another custom tag helper, this time for data input.
Points of Interest
Did you know Angular 2 will not be followed by Angular 3, but instead Angular 4!
History