Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core 2.1 Angular 6.0 SPA template - razor page (cshtml) for index instead static page

I am trying to migrate my ASP.NET Core 2.0 Angular 5 application with webpack setup to ASP.NET Core 2.1 Angular 6 with Angular-Cli.

Short question:

How can I force parsing razor pages directives from cshtml with Microsoft.AspNetCore.SpaServices.Extensions?

I do not want to use index.html from Angular.json, but index.cshtml.

Long description:

I started a new ASP.NET Core project from Angular template. There are quite a few things that just magically works.

  1. There is an index.html file inside ClientApp/src folder which is automatically served when application starts.
  2. When application runs, before </body> tag from index.html (from first point) several scripts are inserted: inline.bundle.js, polyfills.bundle.js, vendor.bundle.js, main.bundle.js and styles.bundle.css before </head> tag.

I am not sure who inserted those scripts, but I want to insert them to Razor page (cshtml).

I tried to use code from my old project:

<!DOCTYPE html>
<html>
<head>
    <base href="/" />
    <title>@ViewData["Title"]</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
@{string googleAnalyticsId = Html.Raw(ViewData["GoogleAnalyticsId"]).ToString();}
@if (!string.IsNullOrEmpty(googleAnalyticsId))
{
    <script>
        (function(i, s, o, g, r, a, m)
        {
           ...
        })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');

        ga('create', '@googleAnalyticsId', 'auto');
    </script>
}
<script>
    @Html.Raw(ViewData["TransferData"])
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/0.8.2/css/flag-icon.min.css" async defer />
<script src="https://apis.google.com/js/platform.js" async defer></script>
<app>
    <!-- loading layout replaced by app after startupp -->
    <div class="page-loading">
        <div class="bar">
            <i class="sphere"></i>
        </div>
    </div>
</app>
</body>
</html>

And point index options in Angular.json to this index.cshtml:

  "architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:browser",
        "options": {
            "outputPath": "wwwroot/dist",
            "index": "Views/Home/Index.cshtml",   //<----- here

Problem is that content (all @ tags) is not processed by ASP.NET Core but it outputs direct content:

<!DOCTYPE html>
<html>
<head>
    <base href="/" />
    <title>@ViewData["Title"]</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
@{string googleAnalyticsId = Html.Raw(ViewData["GoogleAnalyticsId"]).ToString();}
@if (!string.IsNullOrEmpty(googleAnalyticsId))
{
    <script>
...

but it should be:

<!DOCTYPE html>
<html>
<head>
    <base href="/" />
    <title>Test title</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
    <script>

I want to print some variables (from appsettings.json) to the end html page. Since I am already using ASP.NET Core, Razor page seems good solution.

I also try:
to add new RazorPage, C# content (@ directives) are processed, but no-one is inserting Angular scripts.

Response for AndyCunningham answer:

ng build --prod writes this files:

<script type="text/javascript" src="runtime.b38c91f828237d6db7f0.js"></script><script type="text/javascript" src="polyfills.2fc3e5708eb8f0eafa68.js"></script><script type="text/javascript" src="main.16911708c355ee56ac06.js"></script> 

to index.html. How to write this to razor page.

like image 803
Makla Avatar asked Jun 12 '18 10:06

Makla


People also ask

Is Cshtml the same as Razor?

cshtml file indicates that the file is a Razor Page.

What is the difference between Razor view and Razor page?

The difference between them is that View Pages are Razor views that are used to provide the HTML representations (aka views) for services in much the same way View Pages work for MVC Controllers.

What is the difference between ASPX and Cshtml?

One major advantage to aspx compared to cshtml is that you can view and edit the page itself (WUSIWYG kind of) using the design tab. With cshtml files you might as well use notepad to edit your html page. You are working "in the dark".


2 Answers

I'm basing this answer on my experience with React SPA extensions, but it should be helpful for Angular as well, as it uses the same ClientApp/index.html middleware mechanism, and the same IApplicationBuilder.UseSpa entrypoint.

The problem is that when you use app.UseSpa(...), the default implementation of this extension configures a middleware that listens for ALL UI requests and fwds them to index.html.

I've built a quick example of how we get around this for our projects: https://github.com/andycmaj/aspnet_spa_without_indexhtml/pull/1/files should show you what needs to change in order to ignore index.html and use a razor view instead.

Basically, i've re-implemented what UseSpa does, but without attaching the middleware that intercepts and fwds to index.html.

Note that the Home razor view still needs to do the more important things that index.html was doing. Most importantly, it needs to include bundle.js served by the SPA static content middleware.

like image 133
AndyCunningham Avatar answered Sep 20 '22 07:09

AndyCunningham


it's and old question, but I hope the answer help someone

Well, is difficut make that webpack insert the scripts tag in you index.cshtml, but you can include the tags in the index.cshtml like

@{
    ViewData["title"] = "I'am from cshtml";
}
<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>

then you can router your mvc like

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

    routes.MapRoute(
        name: "spa-fallback",
        template: "{*url:regex(^((?!.css|.map|.js|sockjs).)*$)}",
        defaults: new { controller = "Home", action = "Index" });

});

See that all the routes that not correcponding with {controller}{action=Index}/{id?} fall in "spa-fallback" that say that if the file not have .css or .map or .js or .sockjs serve your Home/Index. It's necesary the "regex" for .NET serve the script files necesarys.

The last step is configure your angular.json to maintaine the name of the scripts using outputHashing:all

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "ClientApp": {
      ...
      "architect": {
        "build": {
            ...
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all", //<--add this line
                  ...
            },
            ....
  },
  "defaultProject": "ClientApp"
}

At last we can check if an url is valid or not (else any url server the index page) you can use in startup.cs some like

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)
    {
        ..use your code to check the url...
    }
    if (encontrado)
        await next.Invoke(); //<--continue
    else
    {   //send to an error404 page
        context.Response.ContentType = "text/html";
        await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "error404.html"));
    }
});

NOTE: I put the href of the scripts in absolute path, e.g src="/runtime.js", well in package json we can have several scripts like

"scripts": {
    "ng": "ng",
    "start": "ng serve --base-href=/ --serve-path=/",
    "start:fr": "ng serve --base-href=/fr-FR/ --configuration=fr --serve-path=/fr-FR",
    "build": "ng build",
    ...
  },

If we use --serve-path=fr-FR, the href of script must use this serve-path, e.g. src="/fr-FR/runtime.js"

like image 24
Eliseo Avatar answered Sep 22 '22 07:09

Eliseo