ASP .Core MVC configure application root url - c#

I have two ASP .Core MVC applications that are hosted under the same url.
I've managed to separate them with Nginx so that a certain path goes to app-2, while the rest goes to app-1:
http://host -> app-1
http://host/setup -> app-2
My problem comes when the user connects to app-2, as the application still thinks it's app-root is http://host.
This leads to the client encountering a 404 when for example style sheets are downloaded, since app-2.css exists under http://host/setup/css but the application searches in http://host/css.
The "include"-lines in the .cshtml file in app-2 look like this:
<link rel="stylesheet" type="text/css" href="#Url.Content("~/css/app-2.css")" asp-append-version="true" />
Is there some way of "overriding" or telling app-2 that ~ should refer to <host>/setup/css/ instead of <host>/css/?
I really don't want to hardcode it in case the url changes at some point.

After many hours of searching I found no way of altering the application root for the entire webserver.
What I ended up doing instead was create class PathHelper with options and added it to Startup.cs:
class PathHelper
{
public PathHelper(IOptions<PathHelperOptions> opt)
{
Path = opt.Path;
if (Path.StartsWith('/'))
{
Path = Path[1..];
}
if (!Path.EndsWith('/'))
{
Path = Path + '/';
}
}
public string Path { get; }
}
class PathHelperOptions
{
public string Path { get; set; }
}
# Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
.AddScoped<PathHelper>()
.Configure<PathHelperOptions>(opt =>
{
opt.Path = this.configuration.GetSection("URL_SUFFIX");
});
[...]
}
I then used it in the .cshtml files like this:
#inject PathHelper helper
<link rel="stylesheet" type="text/css" href="#Url.Content(helper.Path + "css/app-2.css")" asp-append-version="true" />

I think the easiest way is to include the base tag in pages coming from 'app-2'.
Try it like this:
<html>
<head>
<base href="http://host/setup">
</head>
Now your relative links are sent to 'app-2'.

Related

Server side rendering. Web API and Angular 2

I've developed a web application built using ASP.NET Core Web API and Angular 4. My module bundler is Web Pack 2.
I would like to make my application crawlable or link sharable by Facebook, Twitter, Google. The url must be the same when some user tries to post my news at Facebook. For example, Jon wants to share a page with url - http://myappl.com/#/hellopage at Facebook, then Jon inserts this link into Facebook: http://myappl.com/#/hellopage.
I've seen this tutorial of Angular Universal server side rendering without tag helper and would like to make server side rendering. As I use ASP.NET Core Web API and my Angular 4 application does not have any .cshtml views, so I cannot send data from controller to view through ViewData["SpaHtml"] from my controller:
ViewData["SpaHtml"] = prerenderResult.Html;
In addition, I see this google tutorial of Angular Universal, but they use NodeJS server, not ASP.NET Core.
I would like to use server side prerendering. I am adding metatags through this way:
import { Meta } from '#angular/platform-browser';
constructor(
private metaService: Meta) {
}
let newText = "Foo data. This is test data!:)";
//metatags to publish this page at social nets
this.metaService.addTags([
// Open Graph data
{ property: 'og:title', content: newText },
{ property: 'og:description', content: newText }, {
{ property: "og:url", content: window.location.href },
{ property: 'og:image', content: "http://www.freeimageslive.co.uk/files
/images004/Italy_Venice_Canal_Grande.jpg" }]);
and when I inspect this element in a browser it looks like this:
<head>
<meta property="og:title" content="Foo data. This is test data!:)">
<meta property="og:description" content="Foo data. This is test data!:)">
<meta name="og:url" content="http://foourl.com">
<meta property="og:image" content="http://www.freeimageslive.co.uk/files
/images004/Italy_Venice_Canal_Grande.jpg"">
</head>
I am bootstrapping the application usual way:
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
and my webpack.config.js config looks like this:
var path = require('path');
var webpack = require('webpack');
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var WebpackNotifierPlugin = require('webpack-notifier');
var isProd = (process.env.NODE_ENV === 'production');
function getPlugins() {
var plugins = [];
// Always expose NODE_ENV to webpack, you can now use `process.env.NODE_ENV`
// inside your code for any environment checks; UglifyJS will automatically
// drop any unreachable code.
plugins.push(new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}));
plugins.push(new webpack.ProvidePlugin({
jQuery: 'jquery',
$: 'jquery',
jquery: 'jquery'
}));
plugins.push(new CleanWebpackPlugin(
[
'./wwwroot/js',
'./wwwroot/fonts',
'./wwwroot/assets'
]
));
return plugins;
}
module.exports = {
devtool: 'source-map',
entry: {
app: './persons-app/main.ts' //
},
output: {
path: "./wwwroot/",
filename: 'js/[name]-[hash:8].bundle.js',
publicPath: "/"
},
resolve: {
extensions: ['.ts', '.js', '.json', '.css', '.scss', '.html']
},
devServer: {
historyApiFallback: true,
stats: 'minimal',
outputPath: path.join(__dirname, 'wwwroot/')
},
module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'tslint-loader',
enforce: 'pre'
},
{
test: /\.ts$/,
loaders: [
'awesome-typescript-loader',
'angular2-template-loader',
'angular-router-loader',
'source-map-loader'
]
},
{
test: /\.js/,
loader: 'babel',
exclude: /(node_modules|bower_components)/
},
{
test: /\.(png|jpg|gif|ico)$/,
exclude: /node_modules/,
loader: "file?name=img/[name].[ext]"
},
{
test: /\.css$/,
exclude: /node_modules/,
use: ['to-string-loader', 'style-loader', 'css-loader'],
},
{
test: /\.scss$/,
exclude: /node_modules/,
loaders: ["style", "css", "sass"]
},
{
test: /\.html$/,
loader: 'raw'
},
{
test: /\.(eot|svg|ttf|woff|woff2|otf)$/,
loader: 'file?name=fonts/[name].[ext]'
}
],
exprContextCritical: false
},
plugins: getPlugins()
};
Is it possible to do server side rendering without ViewData? Is there an alternative way to make server side rendering in ASP.NET Core Web API and Angular 2?
I have uploaded an example to a github repository.
There is an option in Angular to use HTML5 style urls (without hashes): LocationStrategy and browser URL styles. You should opt this URL style. And for each URL that you want to be shared o Facebook you need to render the entire page as shown in the tutorial you referenced. Having full URL on server you are able to render corresponding view and return HTML.
Code provided by #DávidMolnár might work very well for the purpose, but I haven't tried yet.
UPDATE:
First of all, to make server prerendering work you should not use useHash: true which prevents sending route information to the server.
In the demo ASP.NET Core + Angular 2 universal app that was mentioned in GitHub issue you referenced, ASP.NET Core MVC Controller and View are used only to server prerendered HTML from Angular in a more convenient way. For the remaining part of application only WebAPI is used from .NET Core world everything else is Angular and related web technologies.
It is convenient to use Razor view, but if you are strictly against it you can hardcode HTML into controller action directly:
[Produces("text/html")]
public async Task<string> Index()
{
var nodeServices = Request.HttpContext.RequestServices.GetRequiredService<INodeServices>();
var hostEnv = Request.HttpContext.RequestServices.GetRequiredService<IHostingEnvironment>();
var applicationBasePath = hostEnv.ContentRootPath;
var requestFeature = Request.HttpContext.Features.Get<IHttpRequestFeature>();
var unencodedPathAndQuery = requestFeature.RawTarget;
var unencodedAbsoluteUrl = $"{Request.Scheme}://{Request.Host}{unencodedPathAndQuery}";
TransferData transferData = new TransferData();
transferData.request = AbstractHttpContextRequestInfo(Request);
transferData.thisCameFromDotNET = "Hi Angular it's asp.net :)";
var prerenderResult = await Prerenderer.RenderToString(
"/",
nodeServices,
new JavaScriptModuleExport(applicationBasePath + "/Client/dist/main-server"),
unencodedAbsoluteUrl,
unencodedPathAndQuery,
transferData,
30000,
Request.PathBase.ToString()
);
string html = prerenderResult.Html; // our <app> from Angular
var title = prerenderResult.Globals["title"]; // set our <title> from Angular
var styles = prerenderResult.Globals["styles"]; // put styles in the correct place
var meta = prerenderResult.Globals["meta"]; // set our <meta> SEO tags
var links = prerenderResult.Globals["links"]; // set our <link rel="canonical"> etc SEO tags
return $#"<!DOCTYPE html>
<html>
<head>
<base href=""/"" />
<title>{title}</title>
<meta charset=""utf-8"" />
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"" />
{meta}
{links}
<link rel=""stylesheet"" href=""https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/0.8.2/css/flag-icon.min.css"" />
{styles}
</head>
<body>
{html}
<!-- remove if you're not going to use SignalR -->
<script src=""https://code.jquery.com/jquery-2.2.4.min.js""
integrity=""sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=""
crossorigin=""anonymous""></script>
<script src=""http://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.2.0.min.js""></script>
<script src=""/dist/main-browser.js""></script>
</body>
</html>";
}
Please note that the fallback URL is used to process all routes in HomeController and render corresponding angular route:
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
To make it easier to start consider to take that demo project and modify it to fit with your application.
UPDATE 2:
If you don't need to use anything from ASP.NET MVC like Razor with NodeServices it feels more natural to me to host Universal Angular app with server prerendering on Node.js server. And host ASP.NET Web Api independently so that Angular UI can access API on different server. I think it is quite common approach to host static files (and utilize server prerendering in case) independently fro API.
Here is a starter repo of Universal Angular hosted on Node.js: https://github.com/angular/universal-starter.
And here is an example of how UI and web API can be hosted on different servers: https://github.com/thinktecture/nodejs-aspnetcore-webapi. Notice how API URL is configured in urlService.ts.
Also you could consider to hide both UI and API server behind reverse proxy so that both can be accessed through same public domain and host and you don't have to deal with CORS to make it work in a browser.
Based on your linked tutorials you could return the HTML directly from the controller.
The prerendered page will be available at http://<host>:
[Route("")]
public class PrerenderController : Controller
{
[HttpGet]
[Produces("text/html")]
public async Task<string> Get()
{
var requestFeature = Request.HttpContext.Features.Get<IHttpRequestFeature>();
var unencodedPathAndQuery = requestFeature.RawTarget;
var unencodedAbsoluteUrl = $"{Request.Scheme}://{Request.Host}{unencodedPathAndQuery}";
var prerenderResult = await Prerenderer.RenderToString(
hostEnv.ContentRootPath,
nodeServices,
new JavaScriptModuleExport("ClientApp/dist/main-server"),
unencodedAbsoluteUrl,
unencodedPathAndQuery,
/* custom data parameter */ null,
/* timeout milliseconds */ 15 * 1000,
Request.PathBase.ToString()
);
return #"<html>..." + prerenderResult.Html + #"</html>";
}
}
Note the Produces attribute, which allows to return HTML content. See this question.

Set 'cshtml' data and return a string

I have a confirmation email template .cshtml as following:
<html>
<head>
<title>company name: Email Verification - Action Required</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
Thanks for signing up for company name!
<br/>
<br/>
Thank you for signing up to company name. Please press the link below to confirm your email address <br/>
If you did not sign up for company name, please ignore this email, and accept our apologies for the inconvenience.
<br/>
<br/>
#Model.ConfirmLink
<br/>
<br/>
<br/>
Thanks,<br/>
The <a href="http://company name.com" >company name</a> Team
</table>
</body>
</html>
Is there a mechanism I can set data (#Model.ConfirmLink) and get back a result as string? (not manually replace string)
You can use RazorEngine, which uses the same syntax as Razor does, but gives you the ability to render the text without the need of ASP.NET MVC.
How your code would work:
string template = yourHtmlCodeAsString;
string result = Razor.Parse(template, new { ConfirmLink = "http://.../" });
You can load that template string from a file, a resource, etc.
Yes, you can use Razor as template engine for emails.
I use this class.
public static class EmailFactory
{
public static string ParseTemplate<T>(T model, string templatesPath, EmailType emailType)
{
string templatePath = Path.Combine(templatesPath, string.Format("{0}.cshtml", emailType));
string content = templatePath.ReadTemplateContent();
return Razor.Parse(content, model);
}
}
templatesPath - is the path where my cshtml views for email are.
EmailType is and enum that has elements that are the same as the view namse from the templatesPath directory.
The essence is Razor.Parse(content, model); it takes a string that is a valid razor view and a model and merges them together. Same as the views in ASP MVC.

Orchard. How to render something before 'HeadScripts' shape?

Sometimes javascript code needs some data from server (e.g. constants or configs shared between JS and C#). I don't want to pull this data via Ajax. I want to render this data into html page on server.
Let's say, I have a component which consits of C# server-side code and JS client-side code. Component defines some C# constants to be provided for a specific JS code. This definition can be done somehow in ResourceManifest. Defined C# constants should be rendered into html before JS script links. The first part of JS links is rendered in 'HeadScripts' shape.
The question is how to render something before 'HeadScripts' shape?
I tried to wrap 'HeadScripts' shape. But it didn't help.
Wrapper in IShapeTableProvider implementation:
builder.Describe("HeadScripts").Configure(desc => desc.Wrappers.Add("HeadScriptsWrapper"));
HeadScriptsWrapper.cshtml:
<script>
var ServerConstants = {
const1: 'value1',
const2: 'value2'
};
</script>
#Display(Model.Child)
The result is:
...
<script src="/.../jquery-1.11.1.js" type="text/javascript"></script>
<script src="/.../some.js" type="text/javascript"></script>
<script src="/.../onemore.js" type="text/javascript"></script>
...
<meta content="Orchard" name="generator" />
<meta content="utf-8" name="charset" />
...
<link href="/.../favicon.png" rel="shortcut icon" type="image/x-icon" />
<script>
var ServerConstants = {
const1: 'value1',
const2: 'value2'
};
</script>
As you can see code from wrapper is rendered after 'HeadScripts'.
Please help.
I've managed to render custom string before 'HeadScripts' shape.
public class BeforeHeadScriptsShapeProvider : IShapeTableProvider
{
private readonly Work<IOrchardServices> orchardServices;
public BeforeHeadScriptsShapeProvider(Work<IOrchardServices> orchardServices)
{
this.orchardServices = orchardServices;
}
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("HeadScripts")
.OnCreated(created =>
{
orchardServices.Value.WorkContext.Layout.Head.Add(created.New.BeforeHeadScriptsShape());
});
}
[Shape]
public void BeforeHeadScriptsShape(HtmlHelper Html)
{
Html.ViewContext.Writer.WriteLine("<script type=\"text/javascript\"> alert('TEST');</script>");
}
}
I can't explain this code in details, but I've found that some shapes are rendered through TextWriter Output parameter. But Output always renders after 'HeadScripts'. 'HeadScripts' (which is a set of 'Script' shapes) uses writer from HtmlHelper Html parameter. So, using HtmlHelper Html allows you to render custom content before 'HeadScripts'.

How to get return value of method into <link> element in ASP.NET WebForms?

I want to add ticks of modification date of CSS file to the link.
I wrote some code that returns the ticks of modification date:
protected string GetFoundationCssModifiedDateTicks()
{
var cssPath = Server.MapPath("~/css") + "\\" + "foundation.min.css";
return GetFileModifiedDateTicks(cssPath).ToString();
}
private long GetFileModifiedDateTicks(string fileName)
{
return !File.Exists(fileName) ? 0 : File.GetLastWriteTime(fileName).Ticks;
}
changed declaration of CSS file in ASPX page:
<link rel="stylesheet" href="/css/foundation.min.css?<%= GetFoundationCssModifiedDateTicks() %>"/>
But it doesn't work and returns the following to browser:
<link rel="stylesheet" href="/css/foundation.min.css?<%= GetFoundationCssModifiedDateTicks() %>" />

How to add content to sections programmatically in MVC / ASP.NET?

In each of my views, I am constantly adding a style and script associated with the view. It is convention in the application we introduced, but we want to automatically apply this convention instead.
Currently our views look like this:
<p>This is the view!</p>
#section styles {
<link rel="stylesheet" href="~/Views/Home/Index.css" />
}
#section scripts {
<script src="~/Views/Home/Index.js"></script>
}
Is there a way to automate adding the styles and scripts? Perhaps in the base class of the controller, such as:
public class BaseController : Controller
{
string controller = RouteData.Values["controller"].ToString();
string action = RouteData.Values["action"].ToString();
string script = string.Format("<script src=\"~/Views/{0}/{1}.js\"></script>", controller, action);
string style = string.Format("<link rel=\"stylesheet\" href=\"~/Views/{0}/{1}.css\" />", controller, action);
// Todo: How to add script and style to sections programmatically???
}
You can use the controller/action name values in your views, and specifically, in your layout. Just edit your layout and in the head add:
<link rel="stylesheet" href="~/Views/#(ViewContext.RouteData.Values["controller"])/#(ViewContext.RouteData.Values["action"]).css" />
And before your closing body tag, add:
<script src="~/Views/#(ViewContext.RouteData.Values["controller"])/#(ViewContext.RouteData.Values["action"]).js"></script>

Categories

Resources