Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
424 views
in Technique[技术] by (71.8m points)

single page application - .NET core 2.1 base href tag on Angular template

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 folders, for example /it/myapp, or /aa/myotherapp

On the 2.0 template the base href property would be set automatically, I'm assuming because it was built with razor, like this:

<base href="~/" />

However this is not true for the 2.1 template, I'm assuming it's because the template actually only uses static files, with the new app.UseSpa()

Any ideas on how I could automatically populate the base href tag?

Thank you

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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-devkituild-angularsrcangular-cli-filesplugins

  • browser.js from node_modules@angular-devkituild-angularsrcangular-cli-filesmodelswebpack-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 {}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...