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.
index.html
file inside ClientApp/src
folder which is
automatically served when application starts.</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.
cshtml file indicates that the file is a 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.
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".
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.
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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With