2
votes

I have a sample MVC6 single page app with one view in which I want to load 2 Angular partials using ngRoute. You can have a look at it at GitHub

There are 3 URLs in the app:

  1. localhost - Index.cshtml
  2. localhost/games - Index.cshtml with Angular's gamelist.html partial
  3. localhost/games/2 - Index.cshtml with Angular's game.html partial

The routes config is the following:

MVC:

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

            routes.MapRoute("gamelist", "games", new { controller = "Home", action = "Index"});
            routes.MapRoute("gameWithId", "games/2", new { controller = "Home", action = "Index" });
        });

Angular:

myApp.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
    $routeProvider
        .when('/games', {
            templateUrl: 'partials/gameslist.html',
            controller: 'GameController',
            controllerAs: 'ctrl'
        })
        .when('/games/:gameId', {
            templateUrl: 'partials/game.html',
            controller: 'GameController',
            controllerAs: 'ctrl'
        });

    $locationProvider.html5Mode(true);
}]);

It all works perfectly fine as long as I start the app from the home page '/' and then navigate to the partials using the links on the page. The problem is that the URL #3 (localhost/games/2) does not work if I start the app from it, by typing it in the address bar. The URL #2 (/games/) does work.

The reason why #3 does not work is that MVC removes '/games' part from the URL and what Angular gets is just '/2'. If you run the sample app, you will see that '$location.path = /2'. Of course Angular cannot map using that path and no partial is rendered. So my question is - how to make MVC return the full path to the client so that Angular can map it?

3
Actually as you use the same response for each request, maybe it's better to make something like this: stackoverflow.com/questions/19643001/…BotanMan

3 Answers

2
votes

You can get it to work with HTML5 mode, you just need to ensure that every request maps back to your Index.cshtml view. At that point the AngularJS framework loads, client-side routing kicks in and evaluates the request URI and loads the appropriate controller and view.

We've done this with multiple Angular apps inside MVC with different .cshtml pages, though we use attribute routing with the wildcard character, e.g.

[Route("{*anything}")]
public ActionResult Index()
{
    return View("Index");
}

The wildcard operator (*) tells the routing engine that the rest of the URI should be matched to the anything parameter.

I haven't had chance to get to grips with MVC6 yet but I think you can do something like this with the "new" version of attribute routing?

[HttpGet("{*anything:regex(^(.*)?$)}"]
public ActionResult Index()
{
    return View("Index");
}
0
votes

To make link #3 work from the browser's address bar, I turned off "html5Mode" in Angular and made links #-based.

0
votes

kudos to this blog

I think it is a better solution.

His solution is rewriting the request that doesn't fit to any route and doesn't have any extension to the landing page of angular.

Here is the code.

public class Startup
{
    public void Configure(IApplicationBuilder app, IApplicationEnvironment environment)
    {
        // Route all unknown requests to app root
        app.Use(async (context, next) =>
        {
            await next();

            // If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
            // Rewrite request to use app root
            if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
            {
                context.Request.Path = "/app/index.html"; // Put your Angular root page here 
                await next();
            }
        });

        // Serve wwwroot as root
        app.UseFileServer();

        // Serve /node_modules as a separate root (for packages that use other npm modules client side)
        app.UseFileServer(new FileServerOptions()
        {
            // Set root of file server
            FileProvider = new PhysicalFileProvider(Path.Combine(environment.ApplicationBasePath, "node_modules")),
            // Only react to requests that match this path
            RequestPath = "/node_modules", 
            // Don't expose file system
            EnableDirectoryBrowsing = false
        });
    }
}