10

Technology:

  • Visual Studio 2017
  • Asp.Net Core Tooling 1.1
  • .Net Framework 4.6.2

Single Page Application's and their integration into Visual Studio have become easier, with all the new support built into Visual Studio. But earlier in the week, Jeffery T. Fritz released a really nice article on the integration and implementing with the following packages:

  • Microsoft.AspNetCore.SpaServices
  • Microsoft.AspNetCore.NodeServices

After you go through and even analyze a couple of the templates, you'll notice in your Solution Explorer, a directory called ClientApp. This is configured, and routed via Webpack.

Inside the Startup.cs is where the issue reveals itself.

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
            {
                HotModuleReplacement = true
            });
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();

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

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

Inside our request, we have some routing to our MVC Framework.

The question, why do we need to specify this route? If we simply utilize the app.UseDefaultFiles() and app.UseStaticFiles() and have our index specified in our wwwroot. Our client side router will always be returned. So why do we not do it this way:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
            {
                HotModuleReplacement = true
            });
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseDefaultFiles();
        app.UseStaticFiles();
    }

I understand that items directly in our wwwroot aren't compressed, also can leak security, but aside from those two drawbacks why not use this route to ensure your index and client side router aren't always returned?

I'm specifically asking about the concept of forcing MVC to always return a Home/Index or UseDefaultFiles() / UseStaticFiles(). What am I missing, why are we being told to force MVC to return it?

Important: Basically the issue targeted, is how to force the index to be returned so our client side framework's router is handling the changes vs the backend returning a specific view state.

Greg
  • 11,302
  • 2
  • 48
  • 79
  • His first name is "Jeffrey" and the article is at: https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp-net-core-with-javascriptservices/ – John Pankowicz Mar 14 '17 at 02:00

2 Answers2

4

These SPA templates all use a minimal ASP.NET Core MVC server foundation. We need the default route for MVC to generate the html markup from Index.cshtml.

I don't see any plain html files being generated in the template's wwwroot/dist folder. So app.UserDefaultFiles() has no Index.html to go to.

I could speculate that maybe the mvc routes are there also in case someone decides to incorporate more controllers/actions/view for a multi-page SPA application (oxy-moron); or to get started with the back end wep api...?

I'm not as familiar with MapSpaFallbackRoute, but it serves as a catch-all, or catch-some, for certain routes not covered by the SPA. See source code: https://github.com/aspnet/JavaScriptServices/blob/dev/src/Microsoft.AspNetCore.SpaServices/Routing/SpaRouteExtensions.cs

Just to be holistic, let's mention that if we want to be server-less, static-file-only, serve-it-from-a-CDN- there are starter templates and an angular cli to boot:

https://github.com/AngularClass/angular2-webpack-starter

https://github.com/angular/angular2-seed

https://cli.angular.io/

But, if these static/mvc-less Javascript apps are hosting in IIS, they will need a web.config with minimal routing setup. See:

https://github.com/angular/angular/issues/11046

EDIT:

Another reason might be that many people would be utilizing the web api in these projects, and for that we would still need to app.UseMvc();

travis.js
  • 5,193
  • 1
  • 24
  • 21
  • But if you store a static index, or default it will search for it and return. And according to the documentation, it goes exception, static, authorized, mvc. The Middleware can detect and break out if met sooner. Is this not true? But you don't need to host in IIS with .net core. – Greg Feb 24 '17 at 07:26
  • The index.html would be manually created, to return. The static page. – Greg Feb 25 '17 at 20:36
  • You may run into problems for refreshing the pages (i.e. deep linking); without out some sort of routing mechanism to the index.html page, you might get 404 errors – travis.js Feb 26 '17 at 22:35
  • If it's static, but the vue.js router for instance and vuex dats store should be holding that though. – Greg Feb 27 '17 at 04:16
  • Also you can host Angular 2 without web.config, you use the URL Rewrite, so several conceptual notions appear to be misrepresented for this question. – Greg Mar 02 '17 at 14:17
3

It seems to be all stuff we have seen before just more nicely packaged up.

    // allow returning of physical files on the file system of the web root and it's sub dirs
    app.UseDefaultFiles();
    app.UseStaticFiles();

    // map MVC routes to controller actions when a static file cannot be found
    app.UseMvc(routes =>
    {
       // default routing convention for mapping "most situations"
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");

        // fallback routing 
        routes.MapSpaFallbackRoute(
            name: "spa-fallback",
            defaults: new { controller = "Home", action = "Index" });
    });

As per comments ...

It really is a fallback should a static file not exist. But, if it does contain a index.htm in the wwwroot, it would technically never execute that route unless particular header info is passed

War
  • 8,539
  • 4
  • 46
  • 98
  • I like your answer, so in essence it really is a fallback should a static file not exist. But, if it does contain a `index.htm` in the *wwwroot*, it would technically never execute that route unless particular header info is passed. Could you modify to reflect that as well please? – Greg Mar 06 '17 at 14:45
  • Ok there we go :) – War Mar 07 '17 at 16:46
  • Thank you. Sadly, the answer below yours has a lot of misinformation in it. – Greg Mar 07 '17 at 17:37