Configuring ASP.NET Core for Angular 2 Deep Linking

Angular 2 default routing uses history.PushState which allows us to use more natural routes such as “” instead of “”.  We could still configure Angular 2 to use all state hash bang routes as well, but all modern supported browsers support pushState already, so why go with less supported configuration?  So, we should go on assumption that for Angular 2 apps we want to use hash bang free URLs.  Once we write this code, everything works great.

If we write configuration for ASP.NET Core, formerly known as MVC 5, here is how our start up method looks.

public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseStaticFiles(); app.UseMvc(config => { config.MapRoute("Default", "{controller}/{action}/{id?}", new { controller = "Home", action = "Index" }); }); }

Now we just need to add home controller with index action to serve our Angular 2 shell page, and we are ready to go.

public class HomeController: Controller { public IActionResult Index() { return View(); } }

You notice that it has just one method.  It serves up main page.  The main page uses _Layout.cshtml in which we load all the scripts, including Angular.

<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/node_modules/bootstrap/bootstrap.min.css" rel="stylesheet"/> <base href="~/"> </head> <body> <div class="container"> @RenderBody() </div> <script> (function (myAngularApp) { (function (config) { config.rootUrl = '@Url.Content("~/")'; console.log("added configuration. Config data is " + JSON.stringify(window.myAngularApp.config)); })(window.myAngularApp.config || (window.myAngularApp.config = {})) })(window.myAngularApp || (window.myAngularApp = {})) </script> <script src="~/node_modules/angular2/bundles/angular2-polyfills.min.js"></script> <script src="~/node_modules/systemjs/dist/system.src.js"></script> <script src="~/node_modules/rxjs/bundles/Rx.js"></script> <script src="~/node_modules/angular2/bundles/"></script> <script src="~/node_modules/angular2/bundles/"></script> <script src="~/node_modules/angular2/bundles/"></script> <script src="~/node_modules/jquery/jquery.min.js"></script> <script src="~/node_modules/bootstrap/bootstrap.min.js"></script> <script> System.config({ packages: { app: { format: 'register', defaultExtension: 'js', } } }); System.import('app/shell/bootstrapper') .then( function () { console.log('started application'); }, function () { console.log('error starting application'); }); </script> </body> </html>

If we look at the Index.csthml view, it just has main Angular 2 component, which server as Angular 2 application’s shell page.


We configure a JavaScript object in _Layout page based on server side settings.  Once this page is served, Angular 2 takes over, and from that point on we do not use much of Asp.Net MVC, other than maybe creating Angular 2 templates based on MVC controllers and partial views, which is always an option, and a convenient one at times.  If we run this app, everything works great until we try deep linking to an Angular 2 route.  For example, we have an Angular route “/contacts/2”.  If we put in a URL, we will get 404 from the server.  This is valid because we did not tell ASP.NET Core that we want to serve the same shell as with the root route.  How do we do this?  Very simple, just add a catch all route.

public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseStaticFiles(); app.UseMvc(config => { config.MapRoute("Default", "{controller}/{action}/{id?}", new { controller = "Home", action = "Index" }); config.MapRoute("AngularDeepLinkingRoute", "{*url}", new { controller = "Home", action = "Index" }); }); }

You can see my second route will catch all possible URLs and redirect them to the home controller.  We could remove the first route, but we need to keep it if we want to serve Angular 2 templates as partial views. If we test our code now, we will see that deep linking now works as expected.  The routes in ASP.NET are evaluated in the order where were added, so our catch all route must be the last.



Leave a Reply to Tim Murphy (@timpmurphy) Cancel reply

Your email address will not be published. Required fields are marked *