.NET core 2.1 base href tag on Angular template

前端 未结 1 858
灰色年华
灰色年华 2020-12-19 14:08

I\'m building a template for our team on top of the .NET Core 2.1 + Angular 5 template included in the latest release of core 2.1, we deploy applications into virtual folder

相关标签:
1条回答
  • 2020-12-19 14:44

    Yes, I know it's an old question, but I hope this help someone more. The answer is about Angular 6 and .NET Core 2.1. Advice: the explanation is a bit large (sorry)

    In Core 2.1, we have a tipical Startup.cs

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
         }
         app.UseHttpsRedirection();
         app.UseStaticFiles();
         app.UseMvc(routes =>
         {
              routes.MapRoute(
              name: "default",
              template: "{controller}/{action=Index}/{id?}");
          });
          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.Options.DefaultPage = $"/index.html";
    
               if (env.IsDevelopment())
               {
                   spa.UseAngularCliServer(npmScript: "start");
               }
          });
      }
    

    The important part is the

       spa.UseAngularCliServer(npmScript: "start");
    

    this tell to .NET that use the script defined in package.json

    {
      "name": "client-app",
      "version": "0.0.0",
      "scripts": {
        "ng": "ng",
        "start": "ng serve --base-href=/ --serve-path=/", //<--this line
        "build": "ng build",
         ...
      },
      ....
    }
    

    See that we have two options. --base-href and --serve-path

    When we execute the application, we received an index.html like

    <!DOCTYPE html>
    <html>
    <head>
      <title>Angular i18n example</title>
      <base href="/"
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <app-root>Loading...</app-root>
        <script type="text/javascript" src="runtime.js"></script>
        <script type="text/javascript" src="polyfills.js"></script>
        <script type="text/javascript" src="styles.js"></script>
        <script type="text/javascript" src="vendor.js"></script>
        <script type="text/javascript" src="main.js"></script>
    </body>
    </html>
    

    We can change the base href change our package.json. But if not change the --serve-path according, our app fails because we are looking for the script according to the base href.

    The idea is that the scripts not depending for base href. I want that the src of scripts was in absolute path. That's the scripts becomes like

        <script type="text/javascript" src="/<serve-path>/runtime.js"></script>
        <script type="text/javascript" src="/<serve-path>/polyfills.js"></script>
        <script type="text/javascript" src="/<serve-path>/styles.js"></script>
        <script type="text/javascript" src="/<serve-path>/vendor.js"></script>
        <script type="text/javascript" src="/<serve-path>/main.js"></script>
    

    So, if we write in our .html some like

    <head>
      <title>Angular i18n example</title>
      <script>
         var str = document.location.href;
         ...
         document.write('<base href="' + str+ '" />');
      </script>
      <!--we remove base href="/"
          <base href="/"
      -->
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    

    Our scripts are looking for from the correct path and the application works. ¿How change the way to add the scripts? Well, we have to make a few changes in two files:

    • index-html-webpack-plugin.js from node_modules\@angular-devkit\build-angular\src\angular-cli-files\plugins

    • browser.js from node_modules\@angular-devkit\build-angular\src\angular-cli-files\models\webpack-configs

    See first the class IndexHtmlWebpackPlugin in index-html-webpack-plugin.js

    class IndexHtmlWebpackPlugin {
        constructor(options) {
            this._options = Object.assign({ input: 'index.html', output: 'index.html', entrypoints: ['polyfills', 'main'], sri: false }, options);
        }
        apply(compiler) {
          ....
            const scriptElements = treeAdapter.createDocumentFragment();
            for (const script of scripts) {
                const attrs = [
                    { name: 'type', value: 'text/javascript' },
                    //change the line
                    //{ name: 'src', value: (this._options.deployUrl || '') + script },
                    //by 
                    { name: 'src', value: (this._options.baseHref)+(this._options.deployUrl || '') + script },
                ];
                if (this._options.sri) {
                    const content = compilation.assets[script].source();
                    attrs.push(...this._generateSriAttributes(content));
                }
                const element = treeAdapter.createElement('script', undefined, attrs);
                treeAdapter.appendChild(scriptElements, element);
            }
    

    see how we add the this._options.baseHref to the scripts Well, really in constructor we haven't baseHref, it is for we must change the file browser.js in

    plugins: extraPlugins.concat([
        new index_html_webpack_plugin_1.IndexHtmlWebpackPlugin({
            input: path.resolve(root, buildOptions.index),
            output: path.basename(buildOptions.index),
            baseHref: buildOptions.baseHref,
            entrypoints: package_chunk_sort_1.generateEntryPoints(buildOptions),
            deployUrl: buildOptions.deployUrl,
            //we add this line
            servePath: buildOptions.servePath,
            sri: buildOptions.subresourceIntegrity,
        }),
    ]),
    

    One more step. As we are writen the base tag using javascript in our index.html, we don't want that angular-cli add this tag. For this, the only thing that we must change is comment the lines in index-html-webpack-plugin.js

    // Adjust base href if specified
    if (typeof this._options.baseHref == 'string') {
        let baseElement;
        for (const headChild of headElement.childNodes) {
            if (headChild.tagName === 'base') {
                baseElement = headChild;
            }
        }
        const baseFragment = treeAdapter.createDocumentFragment();
        if (!baseElement) {
            baseElement = treeAdapter.createElement('base', undefined, [
                { name: 'href', value: this._options.baseHref },
            ]);
    //coment the two lines below
    //        treeAdapter.appendChild(baseFragment, baseElement);
    //        indexSource.insert(headElement.__location.startTag.endOffset + 1, parse5.serialize(baseFragment, { treeAdapter }));
        }
    

    And that's all !!

    Two more some points of interes

    When we create the base tag using javascript we need ajust the base. Else, if we refresh the page, our base href change and, if we are, e.g in page home, our url will be like http:// www.dominio.com/< something>/< something>/home. We want that the base will go on < something>/< something>. take a look to the script and ajust like your want,e.g.

      <script>
        var items = document.location.href.split('/');
        while (items.length>4)
          items.pop();
        document.write('<base href="' + items.join('/') + '" />');
      </script>
    

    The last point if make that only valid url are server as SPA. For that, we modified our startup.cs make that only valid url was server. (else, some url like http://www.dominio.com/patata/patata serve our SPA) Add between app.UseMvc and app.UseSpa a Middleware

       app.UseMvc(routes =>
       {
        ...
        });
       //Add this Middleware
        app.Use(async (context, next) =>
        {
            string path = context.Request.Path.Value.Substring(1);
            bool encontrado = path.EndsWith(".js") || path.EndsWith(".css") || path.EndsWith(".map") || path.StartsWith("sockjs");
            if (!encontrado)
            {
                //make here the check for the pages
                //"silly" e.g.
                encontrado==(path=="es/adminitrator" || path=="it/administrator")
            }
            if (encontrado)
                await next.Invoke();
            else
            {
                context.Response.ContentType = "text/html";
                await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "error404.html"));
            }
            app.UseSpa(spa=>
            {
                  ....
            }
        });
    

    Update

    Use java script to add tab base it's a really ugly work-around. Better use BASE_REF Provider

    import {Component, NgModule} from '@angular/core';
    import {APP_BASE_HREF} from '@angular/common';
    
    @NgModule({
      providers: [{provide: APP_BASE_HREF, useValue: '/my/app'}]
    })
    class AppModule {}
    
    0 讨论(0)
提交回复
热议问题