Routing in Angular 2 with ASP.NET Core

In my previous post, I explained how to configure Angular 2 with TypeScript in a ASP.NET Core project created with Visual Studio. The idea was to explain the minimum steps that you need in order to have a basic application working. However, you will generally create more complex apps using Angular, for example, a Single Page Application (SPA). That kind of applications, generally take advantages of the routing features that are incorporated in Angular nowadays. In this post, I will show you how to configure routing in an Angular 2 application with ASP.NET Core.Routing in Angular 2 with ASP.NET Core

First of all, lets configure the Angular 2’s Router component in the site. The Router component is not part of the Angular 2 core, it’s in its own library which you can find as part of the bundles included when installing the npm package. In order to make it available in the application, you need to include the script in the layout. To do so, open the _Layout.cshtml file located at Views/Shared/ and add the following code right after the script element that loads Angular 2 core.

<environment names="Development">
    <script src="~/lib/npmlibs/angular2/router.dev.js"></script>
</environment>

<environment names="Staging,Production">
    <script src="~/lib/npmlibs/angular2/router.min.js"></script>
</environment>

Now, in the same file, we must add a <base> element tag to make pushState routing work. The browser also needs the base href value to prefix relative URLs when downloading and linking to css files, scripts, and images. Add this tag after the <head> tag using as value for the href attribute the base url, which will usually be “/”. Note that if you don’t use the application root for your Angular app, you’ll need to update this value accordingly.

<html>
<head>
     <base href="/" />
     <!-- ... -->

By doing this, we added the routing component’s requirements to the application. Now, it’s time to configure your Angular 2 application to use it. First of all, add the Router providers to the bootstrap, by importing the ROUTER_PROVIDERS from angular2/router and passing it to Angular’s bootstrap function.

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
import {ROUTER_PROVIDERS} from 'angular2/router'

bootstrap(AppComponent, [ROUTER_PROVIDERS]);

Now, open the app.component.ts file and import RouteConfig and ROUTER_DIRECTIVES from angular2/router. Additionally, update the component’s template to use the routerLink directive and the router-outlet component. Moreover, add the RouterConfig attribute to configure the links.

The following code shows an example that uses two buttons to redirect to SampleComponent and AnotherSampleComponent components, using the first one as default and redirecting any other urls to that path. My idea is not to explain Angular 2 routing in this post, just focus on the structure needed to integrate it with ASP.NET Core, so if you need more information at this point, you can try on the official documentation.

import {Component} from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
import { SampleComponent, AnotherSampleComponent } from './sample.components';

@Component({
    selector: 'my-app',
    template: `
         <h1>My First Angular 2 App</h1>
         <nav class="btn-group" role="group" aria-label="...">
            <a href="" [routerLink]="['Sample']" class="btn ban-default">Sample</a>
            <a href="" [routerLink]="['AnotherSample']" class="btn ban-default">Another Sample</a>
         </nav>
         <router-outlet></router-outlet>
    `,
    directives: [ROUTER_DIRECTIVES]
})
@RouteConfig([
    { path: '/sample', name: 'Sample', component: SampleComponent, useAsDefault: true },
    { path: '/another-sample', name: 'AnotherSample', component: AnotherSampleComponent },
    { path: '/**', redirectTo: ['Sample'] }
])
export class AppComponent { }

If you run the solution now, you will be able to navigate using the buttons without any issues. The following image shows you the experience.

Routing result

However, you can notice that if you refresh your browser or navigate to a specific path, you will not be able to get to the same page. That’s because in that scenario, the url is first resolved by the server as you are performing a direct request to it and the front-end (the Angular part) can’t handle it at this point. The solution for this is to add a new route configuration in ASP.NET Core.

In the Startup.cs file, locate the app.UseMvc method call and add a new route using {*url} as template and redirecting to the controller that you are using for your Angular 2 application.

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

    routes.MapRoute(
        name: "spa-fallback",
        template: "{*url}",
        defaults: new { controller = "Home", action = "Index" });
});

The problem with this rule is that you will not have 404s for non-existent assets like images, css or js files as the SPA HTML will be returned instead. To avoid this, you can install the Microsoft.AspNet.SpaServices NuGet package and replace the spa-fallback with a call to the MapSpaFallbackRoute method. This will match all the request that doesn’t appear to have a filename extension (defined as ‘having a dot in the last URI segment’). However, it means requests like /customers/isaac.newton will not be mapped into the SPA.

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

    routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});

With this, you have everything you need to create complex Angular 2 applications using ASP.NET Core as backend. You can find the full code in my Angular2ASPNETCoreBaseApp repository at the routing-sample branch.

Updated: I created a new post explaining how to configure Angular2’s Http service and ASP.NET to consume the MVC APIs.

  • Pingback: ASP.NET Core and Angular 2 - Nicolas Bello Camilletti()

  • Pingback: Dew Drop – March 22, 2016 (#2213) | Morning Dew()

  • John Doe

    If we are removing components like routing from MVC, what is the point of using .Net Core? Buisness logic?

    • Just as a clarification, I’m not removing routing from MVC, the SPA-fallback only applies when no other route was found.

      This is used, for example, when you have an API (created using ASP.NET Core in this case), or other backend pages that doesn’t use Angular routing (mixing SPA with the standard MVC pages).

      • bastien

        i just copy/pasted your additional route map route under the default route. Started my app and refreshed the browser then the site stays blank white. You have any idea which it works for me not?

        • Hi!, Did you installed the Microsoft.AspNet.SpaServices NuGet Package? You shouldn’t have any kind of issues if the default route map is catching the request as the routes.MapSpaFallbackRoute method only works when the default route didn’t have a match.

          • bastien

            Hey Nicolas, I did NOT install the “Microsoft.AspNet.SpaServices” with intention, because you wrote its only needed with assets and getting a 404. But as you answered me asking to install it, I just did install it. It is still not working. A wild guess is that in your VS solution you do not use ” app.UseDefaultFiles();” but I use it to statically serve my index.html file. I do not want to use a razor index.cshtml.

          • Oh, sorry about that, I thought you used the routes.MapSpaFallbackRoute method, if you used the first option its not necessary to use the NuGet, as you are using the default methods to create new routes (using the one that define the template as “{*url}”).

            Regarding the UseDefaultFiles method, you should have any issues either with this approach. The thing is that that method is just a URL re-writer that doesn’t actually serve the file, and if you have the UseStaticFiles method call after calling it and before the MVC routes, I think that you should not have any issues (More info in the docs: http://docs.asp.net/en/latest/fundamentals/static-files.html#serving-default-files).

            I hope it helps

          • Bastien

            ” I think that you should not have any issues…” But I have a issue it does not work 🙂 I still thought about what I did this morning and realized it does anyway not make sense… I have a SPA with static index.html on frontend and a API at the backend without HomeController (why should I need it…) For the API I use Attribute Routing only. I do not – NOW – use ANY routes.MapRoute because I do not need them UNTIL I use html pushstate where I fire a url which triggers the server first. In that situation I need to handle the 404, but your code // routes.MapSpaFallbackRoute(“spa-fallback”, new { controller = “Home”, action = “Index” });
            does not help here, because I have no HomeController and I do not want to introduce a razor index.cshtml.

          • Sorry Bastien, honestly I can’t follow you. I will try to reproduce your scenario (no razor html using the UseDefaultFiles and UseStaticFiles methods but with some Controller API) and let you know my findings if you need any extra help.

            However, note that the idea behind the fallback approach is that any request that is performed directly to the server and not handled by your Front End, should return the “angular App”, in your case, that would be the index.html static file. That’s why the fallback route should be the latest one in the pipeline, because first you will need to handle all the routes which you expect to have a different behavior (for example, the static files or the APIs).

          • Bastien

            Hey Nicolas, would be nice to hear from your UseDefaultFiles scenario and wether it worked for you!

          • Sorry, today was a complicated day. Check this: https://github.com/nbellocam/Angular2ASPNETCoreBaseApp/tree/routing-without-mvc. I think that it could help you.

            Basically, as I mentioned before, the useDefaultFiles scenario is not the problem as it is only adding a redirect. The problem was related with the MVC route, as I was specifying in my solution a default MVC route that you didn’t have. So, remove the default values (keeping the template) and also the fallback route. Additionally, add a new middleware to handle the 404 (which will be similar to the fallback route I added in my samples). You can find this middleware in https://github.com/nbellocam/Angular2ASPNETCoreBaseApp/blob/routing-without-mvc/src/MyAngular2BaseApp/Startup.cs#L49

            I hope it helps

          • Bastien

            Thanks for the sample repro. I did exactly those middleware before some weeks, which I got from another blog. I forget the address, but after I told the blog author his code does not work he deleted the post :/ Anyway seeing your solution it works. When I fire my solution it also has that middlware + static index file with angular 2 and systemJS etc… my default url is: http://localhost:55555/schoolyears when I click to ‘administration’ the administration component is loaded. Then I manually delete ‘administration’ in the url and enter instead ‘tests’ then the page reloads and shows this url: http://localhost:55555/tests/schoolyears and the content for ‘schoolyears’ is loaded. I have 3 routes and they all work fine without refresh/manually edit them. One difference is you do not use the $http module. And I use all angular2 cdn beta12. Maybe there is a router bug?

          • Sorry but again, i couldn’t follow you. Is this working for you then? What kind of problem are you having now?

            Regarding the http module, I’m not using it here, but I describe in the following post how to hook it in this solution.

            Regarding the routing bug, everything is possible, as Angular 2 and ASP.NET Core are both still on heavy development. Are you talking about Angular 2 router or ASP.NET router?

          • Bastien

            Hey Nicolas, I had a similar problem now which led me to this blog again and I saw I have not answered your last question. I am sorry for that! Yes it works for me. I do not use the ” if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))” – CODE anymore instead I use your spa fall back route as last route and now my index is returned also when I manually enter the route url. The problem I found now is another that the client side routing seems case sensitive… using the angular router 3. And the base url in index.html is also case sensitive. Both is bad maybe you wanna blog about it 😉

          • Bastien

            I found the bug! You don`t want to know it 😛 In my index.html file I found this: document.write(”); This code is from the angular 2 heroes sample. Then I used your definition:
            NOW it works!

          • Awesome! Thanks for sharing

  • Adrian Wawrzak

    I spent a lot of time, trying to integrate VS with SPA frameworks and it never worked well. There’s always some workaround for yet another problem that exists only, because VS complicate things.
    I started experimenting with VS, Angular 2 + SystemJS and the last problem I remember was with partial html templates, being cached by browser.
    I changed my approach. I switched to Webpack, completely separated my front-end and switched to Visual Studio Code, ASP.NET Core is fine for serving static files. We can deploy files for IIS with “dnu publish”. Webpack comes with dev server, integrates well with Angular 2, importing libraries from NPM is trivial, creating production bundle is also easy and everything finally seems to work.

  • Pingback: Angular 2 Http with ASP.NET Core APIs - Nicolas Bello Camilletti()

  • Chesterpants67

    @adrianwawrzak:disqus. My findings are identical to yours. There is just too much friction trying to get Angular 2 and ASP.NET Core working together effectively in VS2015. I spent weeks trying to get them to live together and whilst I got close (no debugging or intellisense in Angular 2 components) I ended up working on the Angular 2 application using WebStorm and the ASP.NET Core application in VS2015 but even there there were still issues. It also occurred to me that IDE’s like Webstorm are made for this kind of thing, whereas VS2015 is just a bloated and slow IDE that’s seen better days. VS Code is nice but lacks the features of an IDE like Webstorm, which is understandable given it’s only an editor. Ultimately I separated the codebase and introduced a Node ‘GUI Service’ to support client requests and having it orchestrate calls to the ASP.NET Core REST services (and other services).

  • Pingback: Notes from the ASP.NET Community Standup – April 5, 2016 | .NET Web Development and Tools Blog()

  • Roj Beraña

    how about a redirect due to an unauthorized visit of to a certain page?
    im using:

    app.UseCookieAuthentication(options =>
    {…
    options.LoginPath = new PathString(“/unauthorized”);
    options.AccessDeniedPath = new PathString(“/unauthorized”);
    ….
    });

    but this doesnt work. unauthorized view is returned but never loaded in the spa

    • Hi Roj. What do you mean with never loaded in the spa? I’m a bit confused when you said that the unauthorized view is returned but never loaded in the SPA.

      If you are already working in a SPA, you should only be sending request that do not respond with the full HTML page but an API response (normally JSON). In this scenario, your frond end app should receive the unauthorized result and do something with this, it can’t be shown automatically as you are creating this manually.

  • Hello. Is it possible to cause a page refresh to stay on the same page. By default when I refresh it goes back to the home page but I want to be able to stay on the current page if it exist. For the pages that do not exist the user is correctly routed to the home page given the solution you propose.

    • Mmm, that’s strange, The behavior I saw when I created this solution was the one that you are expecting. Are you using a custom solution or my end solution? What versions of the dependencies are you using?

      • Hello Nicolas. I must confess I simply ported your code in parts over to my solution. Let me test your solution and confirm.
        Thanks Zane

        • I have tried everything under the sun to get this to work but can’t find a solution. I have already ported my solution over to RC and can’t get it work. I have tried intercepting the calls to the server as well. Any ideas would be welcome at this stage.

    • Federico Rodriguez

      Zane, I’m having the same issue with Angular 2 RC. Still working to find a solution. I already have the configuration using the second route on the post. Same thing happens if I try to open a new tab from a link, or paste a url directly in the browser search. This was working for me with the previous router from what I can remember

      • Federico Rodriguez

        Well, seems that this line in the AppComponent ngOnInit was the problem this.router.navigate([“/”]);. I put it in the new router because the useAsDefault options doesn’t still exists in the RC Router. Silly mistake :S

      • I read that the Angular 2 RC routing change, so that might be the problem. I didn’t have the chance to use it yet, so if you find anything interesting to share, please do! 🙂 Thanks!

  • Anthony Saliba

    Every time I run this (without changing anything), the browser complains that it can’t find the boot.js file. I am new to TypeScript, is the boot.js file being compiled from boot.ts – and where is it?

  • Alexander Kourbatov

    Hi. First of all thx for the project, it works well if used with dependancies to RC1 of asp.net core. I am wondering if you planning to update this to RC2, since services.AddMvc breaks under it (ambigous call), most likely due to either Newtonsoft’s or SPAFallback packages pulling some stuff in from RC1.

  • Kraig Hamady

    This is great but with one issue… What if you want to have multiple controllers that each have their own SPAs? I am trying to run a separate SPA in two controllers and when I refresh the second page, it will default back to the first page controller (the one that I have in the routes.MapSpaFallbackRoute). Any thoughts?

    • In that scenario you will need to create different fallback routes for each SPA.
      The idea should be to use the MapRoute method and update the template to start with the corresponding url start:

      routes.MapRoute(
      name: “spa-fallback”,
      template: “{mycustomcontroller/*url}”,
      defaults: new { controller = “mycustomcontroller”, action = “Index” });

      I didn’t test this, but that’s the idea you should focus on. Of course the MapSpaFallbackRoute extension method will not work in your scenario.

      A different approach could be to create a custom middleware that handles your scenario.

      • Kraig Hamady

        That seems to be doing the trick… the only issue now is that when I refresh, it adds the mycustomcontroller path into the /app/main.ts so I get an error saying that it cannot find the main.ts. the new path it seems to be looking for is mycustomcontroller/app/main.ts.

        it loads it correctly on clicking a link to the page, but not of refresh.

        Oh, and to correct the above code:
        template: “{mycustomcontroller}/{*url}”,
        The original way throws an error.

        Thanks!

        • mm, you will need to see the system.js documentation to update the root path in that scenario. You are using import ‘./app/main.ts’, right? This is looking the file relative to where you are located right now (which is mycustomcontroller). You might try with ‘~/’, but it seems a bit like a hack to me.

          • Kraig Hamady

            Thanks again for all of the help. I am running into issues with our architecture it seems. We want to serve a home page (home controller) and then have links to two pages (pageone and pagetwo controllers) each controller will have it’s own angular 2 app the issue is that we need to have a URL of http://www.website.com/pageone/ with the app loading in the index of the pageone controller… I’m a bit at a loss here as it seems that all examples I have found use a single controller that does not get added to the URL (using the default .net route) and then use the angular two routing from there. Any thoughts you have would be appreciated.

          • Yes, although it might not seem like a crazy idea, it’s not the usual approach and that’s why you are not finding more information.

            However, I believe that it’s perfectly doable. You just need to verify how system.js resolve the paths. Another possible solution could be to use webpack instead of system.js to create the bundles, but it could be a bit tricker if you don’t know webpack

          • Dave

            Hi Kraig, Did you ever figure out your issue? We want to do something similar. We have different major sections of our site and want to navigate to different parts of the app (Client, Reports, etc) and then each be it’s own “SPA”.

          • Kraig Hamady

            I did! We are using a slightly different approach to template delivery though. We serve all HTML from the .Net side (via controller and IActionResult) for all components and apps. That way, we can set any app to have it’s own controller that matches our angular routing. The only thing we haven’t tried yet is setting up a route in the app for subpages, but I may be starting down that road in the near future… Hope that helps!

  • Alberto Picco

    I think you wanted to write Microsoft.AspNetCore.SpaServices instead of Microsoft.AspNet.SpaServices.
    Anyway I’d like to suggest to include Microsoft.AspNetCore.AngularServices because is built on the SpaServices package (so you get the useful MapSpaFallbackRoute extension) and includes features specific to Angular 2 like “cache priming” feature, which let you pre-evaluate ajax requests on the server so that client-side code doesn’t need to make network calls once it’s loaded.

  • Pingback: angular 2 cdn | bruinrow()