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