41

I have an Angular 5 application that I want to host with Angular Universal on ASP.net Core using the latest Angular template RC. I've followed the docs and have the application up and running. The problem is that I am also using Angular's i18n tools, which produce multiple compiled applications, 1 per locale. I need to be able to host each from https://myhost.com/{locale}/.

I know that I can spin up an instance of the ASP.net Core app for each locale, and set up hosting in the webserver to have the appropriate paths route to the associated app, but this seems excessive to me.

Routes are configured with:

// app is an instance of Microsoft.AspNetCore.Builder.IApplicationBuilder
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action=Index}/{id?}");
});

SpaServices are configured with:

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";

    spa.UseSpaPrerendering(options =>
    {
        options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.bundle.js";
        options.BootModuleBuilder = env.IsDevelopment()
            ? new AngularCliBuilder(npmScript: "build:ssr:en")
            : null;
        options.ExcludeUrls = new[] { "/sockjs-node" };
        options.SupplyData = (context, data) =>
        {
            data["foo"] = "bar";
        };
    });

    if (env.IsDevelopment())
    {
        spa.UseAngularCliServer(npmScript: "start");
    }
});

I've looked through the documentation and the source on Github, and I cannot find how to configure ASP.net Core to associate a specific route with a given SPA. Anyone have any ideas?

asgallant
  • 26,060
  • 6
  • 72
  • 87

1 Answers1

43

Thanks to SteveSandersonMS and chris5287 over on Github for pointing me towards the solution on this.

IApplicationBuilder.Map can segregate paths into different areas of concern. If you wrap a call to app.UseSpa in a call to app.Map, the SPA will be handled only for the path specified by the Map call. The app.UseSpa call ends up looking like:

app.Map("/app1", app1 =>
{
    app1.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";

        spa.UseSpaPrerendering(options =>
        {
            options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.bundle.js";
            options.BootModuleBuilder = env.IsDevelopment()
                ? new AngularCliBuilder(npmScript: "build:ssr:en")
                : null;
            options.ExcludeUrls = new[] { "/sockjs-node" };
            options.SupplyData = (context, data) =>
            {
                data["foo"] = "bar";
            };
        });

        if (env.IsDevelopment())
        {
            spa.UseAngularCliServer(npmScript: "start --app=app1 --base-href=/app1/ --serve-path=/");
        }
    });
});

You can make as many calls to app.Map as necessary to configure your SPAs. Also note the modification to the spa.UseAngularCliServer call at the end: you will need to set --base-href and --serve-path to match your particular config.

Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
asgallant
  • 26,060
  • 6
  • 72
  • 87
  • what is your build:ssr:en and what is app1 refer to. thanks so much – Jean-Francois Feb 12 '18 at 02:41
  • An other thing, should you have a different BootModulePath per language. for exemple ptions.BootModulePath = $"{spa.Options.SourcePath}/dist-server/en/main.bundle.js"; and ptions.BootModulePath = $"{spa.Options.SourcePath}/dist-server/fr/main.bundle.js"; – Jean-Francois Feb 12 '18 at 02:56
  • hre is my build for EN "buildssr-i1n8-en-browser": "ng build --prod --locale=en --i18n-file src/i18n/messages.en.xlf --base-href=/en --deploy-url=/en --output-path=dist/en", "buildssr-i1n8-en-server": "ng build --prod --locale=en --i18n-file src/i18n/messages.en.xlf --base-href=/en --deploy-url=/en --output-path=dist-server/en --app 1 --output-hashing=false", – Jean-Francois Feb 12 '18 at 02:58
  • here is my build for FR "buildssr-i1n8-fr-browser": "ng build --prod --locale=fr --i18n-file src/i18n/messages.fr.xlf --base-href=/fr --deploy-url=/fr --output-path=dist/fr", "buildssr-i1n8-fr-server": "ng build --prod --locale=fr --i18n-file src/i18n/messages.fr.xlf --base-href=/fr --deploy-url=/fr --output-path=dist-server/fr --app 1 --output-hashing=false", – Jean-Francois Feb 12 '18 at 02:58
  • 3
    nothing seem to work on my side . If you could share the entire startup plus the build command, that would be a great help. Thanks a lot – Jean-Francois Feb 12 '18 at 02:59
  • when you publish it, do you need a web.config in the dist folder in order to pointing to the right folder (fr or en) – Jean-Francois Feb 12 '18 at 03:38
  • @Jean-Francois do your scripts work when you run just one locale? Remove the `app.Map` wrapper around the SPA code and run just one locale. I have my build scripts set up to output to `dist/server/{locale}/` and `dist/browser/{locale}/`. I do not have a custom web.config file, just the one generated by the .net Core CLI `publish` command. – asgallant Feb 12 '18 at 15:59
  • Looking at your build scripts, I think you may need to add a trailing `/` to the `base-href` and `deploy-url` parameters: `base-href=/en/` and `deploy-url=/en/`. Angular CLI's `serve` command (called by `spa.UseAngularCliServer` via the `start` script) tests for equality between `deploy-url` and a "normalized" `base-href` (which basically means they force `/`s at the start and end of `base-href`, see [serve.js source](https://github.com/angular/angular-cli/blob/master/packages/%40angular/cli/tasks/serve.ts#L39)). – asgallant Feb 12 '18 at 16:12
  • Thanks for your respond. I guess I need to create one map per lang. What is app1 in your example spa.UseAngularCliServer(npmScript: "start --app=app1 . Thanks a lot – Jean-Francois Feb 12 '18 at 16:15
  • `app1` is just the name given to the app in `angular-cli.json` (via the `name` property of the object in the `apps` array). You can also use `app={index}` if you didn't give your app a name. Since you are building one app with multiple locales and server-side rendering, you probably only have two entries in the `apps` array, one for the browser and one for the server. By default, the build scripts will target the app at index 0 in the array (which is probably the browser app for you). You can probably omit the `--app` parameter from the `start` script. – asgallant Feb 12 '18 at 16:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/164982/discussion-between-jean-francois-and-asgallant). – Jean-Francois Feb 12 '18 at 16:23
  • 1
    I opened a new question here : https://stackoverflow.com/questions/48754386/angular-5-universal-ssr-with-dotnet-core-not-working-with-i18n-angular-tools – Jean-Francois Feb 12 '18 at 19:48
  • Thank you for this! I was missing the --serve-path=/ – SwampyFox May 16 '18 at 18:02
  • In never SPA versions you need to put --base-href= part into package.json start script cmd – zielu1 Jul 01 '19 at 10:19