11

我开发了一个使用ASP.NET Core Web APIAngular 4构建的Web应用程序。我的模块捆绑器是Web Pack 2服务器端渲染。 Web API和Angular 2

我想让我的应用程序可以通过Facebook,Twitter,Google进行抓取或链接共享。当某些用户试图在Facebook上发布我的消息时,url必须相同。例如,Jon想在Facebook分享一个页面 - http://myappl.com/#/hellopage,然后Jon将这个链接插入Facebook:http://myappl.com/#/hellopage

我见过Angular Universal server side rendering without tag helper这个教程,并想进行服务器端渲染。因为我用ASP.NET Core Web API和我Angular 4应用程序没有任何.cshtml意见,所以我不能从控制器发送数据从我的控制器通过ViewData["SpaHtml"]查看:

ViewData["SpaHtml"] = prerenderResult.Html; 

另外,我看到this google tutorial of 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中有没有其他方法可以进行服务器端渲染?

我已经上传an example to a github repository

+0

你试过https://github.com/aspnet/JavaScriptServices了吗? –

+0

@AndriiLitvinov是的,这是需要使用'ViewData'发送HTML到'.cshtml'视图在本教程中,但我只用'.html'看法,并不'.cshtml'意见。 – StepUp

+0

好吧,你为什么认为你需要cshtml视图?为什么不简单地从操作中返回'prerenderResult.Html'? –

回答

4

在Angular中有一个选项可以使用HTML5风格的URL(无散列):LocationStrategy and browser URL styles。你应该选择这种URL风格。对于每个你想共享的URL,你需要按照你所引用的教程所示呈现整个页面。在服务器上有完整的URL,您可以呈现相应的视图并返回HTML。

由@DávidMolnár提供的代码可能工作得很好,但我还没有尝试过。

UPDATE:

首先,使服务器预渲染工作,你不应该使用useHash: true这样可防止发送路由信息到服务器。

在您引用的GitHub问题中提到的演示ASP.NET Core + Angular 2 universal app中,ASP.NET Core MVC控制器和视图仅以更方便的方式用于来自Angular的服务器预呈现的HTML。对于应用程序的其余部分,只有WebAPI在.NET Core世界中使用,其他所有内容都是Angular和相关的Web技术。

可以很方便地使用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'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 [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>"; 
} 

请注意,后备网址被用于处理HomeController全航线渲染与角航线:

builder.UseMvc(routes => 
{ 
    routes.MapSpaFallbackRoute(
     name: "spa-fallback", 
     defaults: new { controller = "Home", action = "Index" }); 
}); 

为了更容易开始考虑采取这一示范项目,并对其进行修改以适合您的应用程序。

更新2:

如果您不需要像剃刀使用任何从ASP.NET MVC与NodeServices感觉更自然的我主持与服务器的Node.js服务器上进行预渲染的通用角应用。并独立承载ASP.NET Web Api,以便Angular UI可以访问不同服务器上的API。我认为这是非常常见的方法来主办静态文件(并利用服务器预渲染的情况下)独立的API。

这里是通用角的启动回购托管在Node.js的:https://github.com/angular/universal-starter

这里是如何的UI和Web API可以在不同的服务器上托管的例子:https://github.com/thinktecture/nodejs-aspnetcore-webapi。请注意0​​中如何配置API URL。你

也可以考虑躲都UI和API服务器后面的反向代理这样既可以通过同一个公共域名和主机进行访问,你不必应付CORS,使其在浏览器中运行。

+0

@StepUp,我已经更新了我的回答,现在回家了。 –

+0

在我看来,你的答案和DávidMolnár之间没有区别。 – StepUp

+0

@StepUp,够公平的。无论如何,你要求一个服务器端渲染的例子,有一个例子。它以什么方式不适合你? –

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内容。请参阅this的问题。

+0

我如何直接使用此网址通过搜索引擎优化?例如,我的页面的浏览器地址是'http://myappl.com /#/ hello',但是SEO应该寻找另一个地址('http:// myappl.com/api/prerenderer')? – StepUp

+0

@StepUp,听起来像你的问题并不能反映你想要的。试着更好地解释你想达到的目标。没有办法使它在URL中使用哈希值,因为浏览器在'#'之后不会将URL的一部分发送到服务器。所有从'http://myappl.com /#/ hello'获得的服务器都是'http:// myappl.com /'。 –

+0

@AndriiLitvinov我想要的是让我的应用程序可以被Facebook,Twitter或Google共享。**为此,我在'mypage中使用了元标记'构造函数(private metaService:Meta){}' .component.ts',但是该页面无法被Facebook抓取和共享。 Twitter或其他社交网络。 – StepUp