Mixed SPA and ASP.NET MVC Routing

Ben Weidberg Source

I'm working on a mixed routing Angular 2 and ASP.NET Core 2 (razor) project. How would you jump out of angular routing and get razor pages? I tried catching all unknown route with angular routing and reloading the unknown route but if there is a route ASP.NET and angular doesn't recognize it goes into a loop. The Configure method of the Startup class contains this.

public void Configure(IApplicationBuilder app)
{
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "Index",
            defaults: new { controller = "controller", action = "Index" },
            template: "{controller}");

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

        routes.MapRoute(
            name: "Login",
            defaults: new { controller = "Sessions", action = "New" },
            template: "Login");
    });

    app.UseSpa(spa =>
    {
        // To learn more about options for serving an Angular SPA from ASP.NET Core,
        // see https://go.microsoft.com/fwlink/?linkid=864501

        spa.Options.SourcePath = "ClientApp";
    });
}

Some examples:

  • Mvc route Mysite.com/documents/view/
  • Angular route Mysite.com/PendingTransactions
c#asp.net-mvcangularasp.net-coreasp.net-core-mvc

Answers

answered 1 week ago Zzz #1

Solution works for MVC 4.

NOTE: You should place your default route after all your other routes, but before your catch all route.

Exclude Angular app from MVC routing (You will notice something funny with the true/false evaluation, this is because the app routing is handled by MVC unless we are in the /app angular application. You can see the opposite implantation here):

routes.MapRouteLowercase(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                constraints: new
                {
                    serverRoute = new ServerRouteConstraint(url =>
                    {
                        var isAngularApp = false;
                        if (url.PathAndQuery.StartsWith("/app",
                            StringComparison.InvariantCultureIgnoreCase))
                        {
                            isAngularApp = true;
                        }               
                        return !isAngularApp;
                    })
                }
            );

The ServerRouteConstraint class:

 public class ServerRouteConstraint : IRouteConstraint
    {
        private readonly Func<Uri, bool> _predicate;

        public ServerRouteConstraint(Func<Uri, bool> predicate)
        {
            this._predicate = predicate;
        }

        public bool Match(HttpContextBase httpContext, Route route, string parameterName,
            RouteValueDictionary values, RouteDirection routeDirection)
        {
            return this._predicate(httpContext.Request.Url);
        }
    }

This is a catch-all for when no other routes matched. Let the Angular router take care of it

    routes.MapRouteLowercase(
        name: "angular",
        url: "{*url}",
        defaults: new { controller = "App", action = "Index" } // The view that bootstraps Angular 5
    );

comments powered by Disqus