Shaun Xu

The Sheep-Pen of the Shaun


News

logo

Shaun, the author of this blog is a semi-geek, clumsy developer, passionate speaker and incapable architect with about 10 years experience in .NET. He hopes to prove that software development is art rather than manufacturing. He's into cloud computing platform and technologies (Windows Azure, Aliyun) as well as WCF and ASP.NET MVC. Recently he's falling in love with JavaScript and Node.js.

Currently Shaun is working at IGT Technology Development (Beijing) Co., Ltd. as the architect responsible for product framework design and development.

MVP

My Stats

  • Posts - 94
  • Comments - 337
  • Trackbacks - 0

Tag Cloud


Recent Comments


Recent Posts


Archives


Post Categories



Microsoft had announced ASP.NET vNext in BUILD and TechED recently and as a developer, I found that we can add features into one ASP.NET vNext application such as MVC, WebAPI, SignalR, etc.. Also it's cross platform which means I can host ASP.NET on Windows, Linux and OS X.

 

If you are following my blog you should knew that I'm currently working on a project which uses ASP.NET WebAPI, SignalR and AngularJS. Currently the AngularJS part is hosted by Express in Node.js while WebAPI and SignalR are hosted in ASP.NET. I was looking for a solution to host all of them in one platform so that my SignalR can utilize WebSocket.

Currently AngularJS and SignalR are hosted in the same domain but different port so it has to use ServerSendEvent. It can be upgraded to WebSocket if I host both of them in the same port.

 

Host AngularJS in ASP.NET vNext Static File Middleware

ASP.NET vNext utilizes middleware pattern to register feature it uses, which is very similar as Express in Node.js. Since AngularJS is a pure client side framework in theory what I need to do is to use ASP.NET vNext as a static file server. This is very easy as there's a build-in middleware shipped alone with ASP.NET vNext.

Assuming I have "index.html" as below.

   1: <html data-ng-app="demo">
   2:     <head>
   3:         <script type="text/javascript" src="angular.js" />
   4:         <script type="text/javascript" src="angular-ui-router.js" />
   5:         <script type="text/javascript" src="app.js" />
   6:     </head>
   7:     <body>
   8:         <h1>ASP.NET vNext with AngularJS</h1>
   9:         <div>
  10:             <a href="javascript:void(0)" data-ui-sref="view1">View 1</a> | 
  11:             <a href="javascript:void(0)" data-ui-sref="view2">View 2</a>
  12:         </div>
  13:         <div data-ui-view></div>
  14:     </body>
  15: </html>

And the AngularJS JavaScript file as below. Notices that I have two views which only contains one line literal indicates the view name.

   1: 'use strict';
   2:  
   3: var app = angular.module('demo', ['ui.router']);
   4:  
   5: app.config(['$stateProvider', '$locationProvider', function ($stateProvider, $locationProvider) {
   6:     $stateProvider.state('view1', {
   7:         url: '/view1',
   8:         templateUrl: 'view1.html',
   9:         controller: 'View1Ctrl' });
  10:  
  11:     $stateProvider.state('view2', {
  12:         url: '/view2',
  13:         templateUrl: 'view2.html',
  14:         controller: 'View2Ctrl' });
  15: }]);
  16:  
  17: app.controller('View1Ctrl', function ($scope) {
  18: });
  19:  
  20: app.controller('View2Ctrl', function ($scope) {
  21: });

All AngularJS files are located in "app" folder and my ASP.NET vNext files are besides it. The "project.json" contains all dependencies I need to host static file server.

   1: {
   2:     "dependencies": {
   3:         "Helios" : "0.1-alpha-*",
   4:         "Microsoft.AspNet.FileSystems": "0.1-alpha-*",
   5:         "Microsoft.AspNet.Http": "0.1-alpha-*",
   6:         "Microsoft.AspNet.StaticFiles": "0.1-alpha-*",
   7:         "Microsoft.AspNet.Hosting": "0.1-alpha-*",
   8:         "Microsoft.AspNet.Server.WebListener": "0.1-alpha-*"
   9:     },
  10:     "commands": {
  11:         "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:22222"
  12:     },
  13:     "configurations" : {
  14:         "net45" : {
  15:         },
  16:         "k10" : {
  17:             "System.Diagnostics.Contracts": "4.0.0.0",
  18:             "System.Security.Claims" :  "0.1-alpha-*"
  19:         }
  20:     }
  21: }

Below is "Startup.cs" which is the entry file of my ASP.NET vNext. What I need to do is to let my application use FileServerMiddleware.

   1: using System;
   2: using Microsoft.AspNet.Builder;
   3: using Microsoft.AspNet.FileSystems;
   4: using Microsoft.AspNet.StaticFiles;
   5:  
   6: namespace Shaun.AspNet.Plugins.AngularServer.Demo
   7: {
   8:     public class Startup
   9:     {
  10:         public void Configure(IBuilder app)
  11:         {
  12:             app.UseFileServer(new FileServerOptions() {
  13:                 EnableDirectoryBrowsing = true,
  14:                 FileSystem = new PhysicalFileSystem(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app"))
  15:             });
  16:         }
  17:     }
  18: }

Next, I need to create "NuGet.Config" file in the PARENT folder so that when I run "kpm restore" command later it can find ASP.NET vNext NuGet package successfully.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <configuration>
   3:   <packageSources>
   4:     <add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" />
   5:     <add key="NuGet.org" value="https://nuget.org/api/v2/" />
   6:   </packageSources>
   7:   <packageSourceCredentials>
   8:     <AspNetVNext>
   9:       <add key="Username" value="aspnetreadonly" />
  10:       <add key="ClearTextPassword" value="4d8a2d9c-7b80-4162-9978-47e918c9658c" />
  11:     </AspNetVNext>
  12:   </packageSourceCredentials>
  13: </configuration>

Now I need to run "kpm restore" to resolve all dependencies of my application.

image

Finally, use "k web" to start the application which will be a static file server on "app" sub folder in the local 22222 port.

image

 

Support AngularJS Html5Mode

AngularJS works well in previous demo. But you will note that there is a "#" in the browser address. This is because by default AngularJS adds "#" next to its entry page so ensure all request will be handled by this entry page.

For example, in this case my entry page is "index.html", so when I clicked "View 1" in the page the address will be changed to "/#/view1" which means it still tell the web server I'm still looking for "index.html".

This works, but makes the address looks ugly. Hence AngularJS introduces a feature called Html5Mode, which will get rid off the annoying "#" from the address bar. Below is the "app.js" with Html5Mode enabled, just one line of code.

   1: 'use strict';
   2:  
   3: var app = angular.module('demo', ['ui.router']);
   4:  
   5: app.config(['$stateProvider', '$locationProvider', function ($stateProvider, $locationProvider) {
   6:     $stateProvider.state('view1', {
   7:         url: '/view1',
   8:         templateUrl: 'view1.html',
   9:         controller: 'View1Ctrl' });
  10:  
  11:     $stateProvider.state('view2', {
  12:         url: '/view2',
  13:         templateUrl: 'view2.html',
  14:         controller: 'View2Ctrl' });
  15:  
  16:     // enable html5mode
  17:     $locationProvider.html5Mode(true);
  18: }]);
  19:  
  20: app.controller('View1Ctrl', function ($scope) {
  21: });
  22:  
  23: app.controller('View2Ctrl', function ($scope) {
  24: });

Then let's went to the root path of our website and click "View 1" you will see there's no "#" in the address.

image

But the problem is, if we hit F5 the browser will be turn to blank. This is because in this mode the browser told the web server I want static file named "view1" but there's no file on the server. So underlying our web server, which is built by ASP.NET vNext, responded 404.

image

To fix this problem we need to create our own ASP.NET vNext middleware. What it needs to do is firstly try to respond the static file request with the default StaticFileMiddleware. If the response status code was 404 then change the request path value to the entry page and try again.

   1: public class AngularServerMiddleware
   2: {
   3:     private readonly AngularServerOptions _options;
   4:     private readonly RequestDelegate _next;
   5:     private readonly StaticFileMiddleware _innerMiddleware;
   6:  
   7:     public AngularServerMiddleware(RequestDelegate next, AngularServerOptions options)
   8:     {
   9:         _next = next;
  10:         _options = options;
  11:  
  12:         _innerMiddleware = new StaticFileMiddleware(next, options.FileServerOptions.StaticFileOptions);
  13:     }
  14:  
  15:     public async Task Invoke(HttpContext context)
  16:     {
  17:         // try to resolve the request with default static file middleware
  18:         await _innerMiddleware.Invoke(context);
  19:         Console.WriteLine(context.Request.Path + ": " + context.Response.StatusCode);
  20:         // route to root path if the status code is 404
  21:         // and need support angular html5mode
  22:         if (context.Response.StatusCode == 404 && _options.Html5Mode)
  23:         {
  24:             context.Request.Path = _options.EntryPath;
  25:             await _innerMiddleware.Invoke(context);
  26:             Console.WriteLine(">> " + context.Request.Path + ": " + context.Response.StatusCode);
  27:         }
  28:     }
  29: }

We need an option class where user can specify the host root path and the entry page path.

   1: public class AngularServerOptions
   2: {
   3:     public FileServerOptions FileServerOptions { get; set; }
   4:  
   5:     public PathString EntryPath { get; set; }
   6:  
   7:     public bool Html5Mode
   8:     {
   9:         get
  10:         {
  11:             return EntryPath.HasValue;
  12:         }
  13:     }
  14:  
  15:     public AngularServerOptions()
  16:     {
  17:         FileServerOptions = new FileServerOptions();
  18:         EntryPath = PathString.Empty;
  19:     }
  20: }

We also need an extension method so that user can append this feature in "Startup.cs" easily.

   1: public static class AngularServerExtension
   2: {
   3:     public static IBuilder UseAngularServer(this IBuilder builder, string rootPath, string entryPath)
   4:     {
   5:         var options = new AngularServerOptions()
   6:         {
   7:             FileServerOptions = new FileServerOptions()
   8:             {
   9:                 EnableDirectoryBrowsing = false,
  10:                 FileSystem = new PhysicalFileSystem(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, rootPath))
  11:             },
  12:             EntryPath = new PathString(entryPath)
  13:         };
  14:  
  15:         builder.UseDefaultFiles(options.FileServerOptions.DefaultFilesOptions);
  16:  
  17:         return builder.Use(next => new AngularServerMiddleware(next, options).Invoke);
  18:     }
  19: }

Now with these classes ready we will change our "Startup.cs", use this middleware replace the default one, tell the server try to load "index.html" file if it cannot find resource.

The code below is just for demo purpose. I just tried to load "index.html" in all cases once the StaticFileMiddleware returned 404. In fact we need to validation to make sure this is an AngularJS route request instead of a normal static file request.

   1: using System;
   2: using Microsoft.AspNet.Builder;
   3: using Microsoft.AspNet.FileSystems;
   4: using Microsoft.AspNet.StaticFiles;
   5: using Shaun.AspNet.Plugins.AngularServer;
   6:  
   7: namespace Shaun.AspNet.Plugins.AngularServer.Demo
   8: {
   9:     public class Startup
  10:     {
  11:         public void Configure(IBuilder app)
  12:         {
  13:             app.UseAngularServer("app", "/index.html");
  14:         }
  15:     }
  16: }

Now let's run "k web" again and try to refresh our browser and we can see the page loaded successfully.

image

In the console window we can find the original request got 404 and we try to find "index.html" and return the correct result.

image

 

Summary

In this post I introduced how to use ASP.NET vNext to host AngularJS application as a static file server. I also demonstrated how to extend ASP.NET vNext, so that it supports AngularJS Html5Mode.

You can download the source code here.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.

Comments

Gravatar # re: Host AngularJS (Html5Mode) in ASP.NET vNext
Posted by Chouteau on 6/10/2014 9:20 PM
Great Work !
Gravatar # re: Host AngularJS (Html5Mode) in ASP.NET vNext
Posted by max on 6/12/2014 2:15 PM
Hi Shaun,
thanks for that great post.
I am working on a bigger angular app in early alpha state running on helios too but i did not try out vnext yet. Looks like vnext can solve some design things we were thinking about a lot. As i am sure, our project will take longer that the release from Microsoft, it could ne worth going the extra mile by working on a prerelease framework. Did you have a chance to play arround with webapi, signalr and EF in vnext yet?
Gravatar # re: Host AngularJS (Html5Mode) in ASP.NET vNext
Posted by Shaun Xu on 6/12/2014 2:36 PM
@max
Not now but I plan to try to host WebAPI, SignalR on vNext maybe when Beta came. Currently only VS 2014 CTP supports ASP.NET vNext but it's not recommended to install on your working machine. I'd like to wait for a more stable vNext came then continue try WebAPI, SignalR.
Gravatar # re: Host AngularJS (Html5Mode) in ASP.NET vNext
Posted by Shifatullah on 6/12/2014 10:31 PM
Hi Shaun, are you running AngularJS in Node.JS on server? if yes what benefit it gives because as per my understanding AngularJS is client-side technology.
Gravatar # re: Host AngularJS (Html5Mode) in ASP.NET vNext
Posted by Shaun Xu on 6/13/2014 8:38 AM
@Shifatullah
Yes I;m running AnuglarJS on Node.js. It's just because I'm not familiar with other web hosting in Linux :P
And as you mentioned AngularJS is a client-side technology so you can host it on any hosting you like, IIS, Apache, Ngnix, Node.js and ASP.NET vNext which now doesn't need IIS.
Gravatar # Some updates
Posted by Ken Van Gilbergen on 7/17/2014 8:36 PM
Thanks Shuan, exactly what I needed.

Here some changes people might need when using newer version of Owin:

//replace IBuilder with IAppBuilder

//replace RequestDelegate with AppFunc and add using on top

using AppFunc = Func<IDictionary<string, object>, Task>;

//use owin environment instead of httpcontext
public async Task Invoke(IDictionary<string, object> environment)
{
IOwinContext context = new OwinContext(environment);
...
}
Post A Comment
Title:
Name:
Email:
Comment:
Verification: