Отображение на стороне сервера. Web API и Angular 2
Я разработал веб-приложение, построенное с использованием ASP.NET Core Web API
и Angular 4
. Мой модуль-модуль Web Pack 2
.
Я хотел бы сделать приложение сканируемым или связанным через Facebook, Twitter, Google. url
должен быть таким же, когда какой-либо пользователь пытается опубликовать мои новости в Facebook. Например, Джон хочет поделиться страницей с url - http://myappl.com/#/hellopage
в Facebook, затем Джон вставляет эту ссылку в Facebook: http://myappl.com/#/hellopage
.
Я видел этот учебник Angular рендеринг на стороне сервера без поддержки тэгов и хотел бы сделать рендеринг на стороне сервера. Поскольку я использую ASP.NET Core Web API
, а мое приложение Angular 4
не имеет каких-либо представлений .cshtml
, поэтому я не могу отправить данные с контроллера для просмотра через ViewData["SpaHtml"]
с моего контроллера:
ViewData["SpaHtml"] = prerenderResult.Html;
Кроме того, я вижу это руководство по Google Angular Universal, но они используют сервер NodeJS
, а не ASP.NET Core
.
Я хотел бы использовать предварительную передачу на стороне сервера. Я добавляю метатеги таким образом:
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" }]);
и когда я проверяю этот элемент в браузере, он выглядит так:
<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>
Я загружаю приложение обычным способом:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
и мой конфигуратор webpack.config.js
выглядит следующим образом:
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()
};
Можно ли выполнить рендеринг на стороне сервера без ViewData
? Есть ли альтернативный способ сделать рендеринг на стороне сервера в ASP.NET Core Web API и Angular 2?
Я загрузил пример в репозиторий github.
Ответы
Ответ 1
В Angular есть опция для использования URL-адресов стиля HTML5 (без хэшей): LocationStrategy и стили URL-адреса браузера. Вы должны выбрать этот стиль URL. И для каждого URL-адреса, который вы хотите использовать в Facebook, вам нужно отобразить всю страницу, как показано в учебнике, на который вы ссылались. Имея полный URL-адрес на сервере, вы можете отображать соответствующее представление и возвращать HTML.
Код, предоставленный @DávidMolnár, может работать очень хорошо для этой цели, но я еще не пробовал.
UPDATE:
Прежде всего, чтобы сделать работу по созданию сервера, вы не должны использовать useHash: true
, который предотвращает отправку информации маршрута на сервер.
В демо ASP.NET Core + Angular 2 универсальное приложение, о котором упоминалось в выпуске GitHub, на который вы ссылались, ASP.NET Core MVC Controller и View используются только для предварительного прослушивания HTML-кода из Angular более удобным способом. Для остальной части приложения только WebAPI используется из мира .NET Core, все остальное - Angular и связанные с ним веб-технологии.
Удобно использовать представление Razor, но если вы строго против него, вы можете жестко кодировать HTML в действие контроллера напрямую:
[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 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 [email protected]"<!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>";
}
Обратите внимание, что резервный URL-адрес используется для обработки всех маршрутов в HomeController
и отображения соответствующего маршрута Angular:
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
Чтобы упростить запуск, подумайте о том, чтобы принять этот демонстрационный проект и изменить его в соответствии с вашим приложением.
ОБНОВЛЕНИЕ 2:
Если вам не нужно использовать что-либо из ASP.NET MVC, например Razor с NodeServices, для меня становится более естественным использовать приложение Universal Angular с предварительной приостановкой сервера на сервере Node.js. И размещать ASP.NET Web Api самостоятельно, чтобы пользовательский интерфейс Angular мог получить доступ к API на другом сервере. Я думаю, что это довольно распространенный подход к размещению статических файлов (и использование предварительной предварительной настройки сервера в случае) независимо от API.
Вот стартовое репо Universal Angular, размещенное на Node.js: https://github.com/angular/universal-starter.
И вот пример того, как пользовательский интерфейс и веб-API могут размещаться на разных серверах: https://github.com/thinktecture/nodejs-aspnetcore-webapi. Обратите внимание, что URL-адрес API настроен в urlService.ts
.
Кроме того, вы могли бы скрыть, что оба интерфейса и API-сервер за обратным прокси-сервером могут быть доступны через один и тот же общедоступный домен и хост, и вам не нужно иметь дело с CORS, чтобы заставить его работать в браузере.
Ответ 2
На основе ваших связанных учебников вы можете вернуть HTML непосредственно из контроллера.
Предварительная страница будет доступна в 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>";
}
}
Обратите внимание на атрибут Produces
, который позволяет возвращать HTML-содержимое. См. этот вопрос.