ASP.NET MVCビューを文字列としてレンダリングする方法は?


485

2つの異なるビュー(1つはメールとして送信される文字列)を出力し、もう1つはユーザーに表示するページを出力します。

これはASP.NET MVCベータで可能ですか?

私は複数の例を試しました:

1. ASP.NET MVC BetaのRenderPartialからStringへ

この例を使用すると、「HTTPヘッダーが送信された後にリダイレクトできません。」というメッセージが表示されます。

2. MVCフレームワーク:ビューの出力のキャプチャ

これを使用すると、存在しない可能性のあるビューをレンダリングしようとするため、redirectToActionを実行できないようです。ビューを返すと、完全にめちゃくちゃになり、まったく正しく表示されません。

誰かが私が持っているこれらの問題に対するアイデア/解決策を持っていますか、またはより良いものに対する提案がありますか?

どうもありがとう!

以下に例を示します。私がやろうとしていることは、GetViewForEmailメソッドを作成することです。

public ActionResult OrderResult(string ref)
{
    //Get the order
    Order order = OrderService.GetOrder(ref);

    //The email helper would do the meat and veg by getting the view as a string
    //Pass the control name (OrderResultEmail) and the model (order)
    string emailView = GetViewForEmail("OrderResultEmail", order);

    //Email the order out
    EmailHelper(order, emailView);
    return View("OrderResult", order);
}

ティム・スコットからの返事を受け入れました(私が少し変更してフォーマットしました):

public virtual string RenderViewToString(
    ControllerContext controllerContext,
    string viewPath,
    string masterPath,
    ViewDataDictionary viewData,
    TempDataDictionary tempData)
{
    Stream filter = null;
    ViewPage viewPage = new ViewPage();

    //Right, create our view
    viewPage.ViewContext = new ViewContext(controllerContext, new WebFormView(viewPath, masterPath), viewData, tempData);

    //Get the response context, flush it and get the response filter.
    var response = viewPage.ViewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;

    try
    {
        //Put a new filter into the response
        filter = new MemoryStream();
        response.Filter = filter;

        //Now render the view into the memorystream and flush the response
        viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
        response.Flush();

        //Now read the rendered view.
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        //Clean up.
        if (filter != null)
        {
            filter.Dispose();
        }

        //Now replace the response filter
        response.Filter = oldFilter;
    }
}

使用例

コントローラーからの呼び出しで注文確認メールを取得し、Site.Masterの場所を渡すと仮定します。

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);

2
これを強く型付けされたビューでどのように使用できますか?つまり。モデルをページにフィードするにはどうすればよいですか?
Kjensen 2009年

ヘッダーが送信された後はコンテンツタイプを設定できないため、これを使用してJsonResultを後で作成することはできません(Flushがヘッダーを送信するため)。
Arnis Lapsa 09年

正解は1つではないので、私はそう思います。:)私は自分に固有の質問を作成しましたが、それも広く尋ねられる質問であることはわかっていました。
Dan Atkinson、

2
提案された解決策は、MVC 3では動作しません
カスパーHoldum

1
@Qua:提案されたソリューションは2年以上前のものです。MVC 3でも動作するとは思いません!その上、今これを行うより良い方法があります。
Dan Atkinson、

回答:


572

ここに私が思いついたものがあり、それは私のために働いています。コントローラの基本クラスに次のメソッドを追加しました。(これらの静的メソッドは、コントローラーをパラメーターとして受け入れる他の場所にいつでも作成できます)

MVC2 .ascxスタイル

protected string RenderViewToString<T>(string viewPath, T model) {
  ViewData.Model = model;
  using (var writer = new StringWriter()) {
    var view = new WebFormView(ControllerContext, viewPath);
    var vdd = new ViewDataDictionary<T>(model);
    var viewCxt = new ViewContext(ControllerContext, view, vdd,
                                new TempDataDictionary(), writer);
    viewCxt.View.Render(viewCxt, writer);
    return writer.ToString();
  }
}

Razor .cshtmlスタイル

public string RenderRazorViewToString(string viewName, object model)
{
  ViewData.Model = model;
  using (var sw = new StringWriter())
  {
    var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext,
                                                             viewName);
    var viewContext = new ViewContext(ControllerContext, viewResult.View,
                                 ViewData, TempData, sw);
    viewResult.View.Render(viewContext, sw);
    viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
    return sw.GetStringBuilder().ToString();
  }
}

編集:Razorコードを追加しました。


31
ビューを文字列にレンダリングすることは、ルーティングとは何の関係もないため、常に「ルーティングの概念全体と矛盾しています」。うまくいく答えがなぜ反対票を投じたのか、私にはわかりません。
Ben Lesh

4
Razorバージョンのメソッド宣言から「静的」を削除する必要があると思います。そうしないと、ControllerContextなどを見つけることができません。
Mike

3
これらの余分な空白を削除するには、独自の削除方法を実装する必要があります。私の頭の上から考えることができる最良の方法は、文字列をXmlDocumentに読み込み、最後のコメントで残したリンクに従って、XmlWriterを使用して文字列に書き戻すことです。それがお役に立てば幸いです。
Ben Lesh

3
うーん、WebApiコントローラーを使用してこれを行うにはどうすればよいですか。提案があれば教えてください
Alexander

3
こんにちは、すべてのコントローラーで "Static"キーワードを使用してこのメ​​ソッドを使用することを一般的にするには、静的クラスを作成する必要があります。ここで、stackoverflow.com / a / 18978036/2318354 it を確認できます。
Dilip0165 2013

68

この答えは私の途中ではありません。これは元々https://stackoverflow.com/a/2759898/2318354からのものです が、ここでは「静的」キーワードと一緒に使用して、すべてのコントローラーで共通にする方法を示しています。

そのためにはstatic、クラスファイルでクラスを作成する必要があります。(クラスファイル名がUtils.csであるとします)

この例はRazorの場合です。

Utils.cs

public static class RazorViewToString
{
    public static string RenderRazorViewToString(this Controller controller, string viewName, object model)
    {
        controller.ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
            var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);
            viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View);
            return sw.GetStringBuilder().ToString();
        }
    }
}

これで、コントローラーに「this」をパラメーターとして渡して次のようにコントローラーファイルにNameSpaceを追加することで、コントローラーからこのクラスを呼び出すことができます。

string result = RazorViewToString.RenderRazorViewToString(this ,"ViewName", model);

@Sergeyによって提案されたように、この拡張メソッドはcotrollerから以下のように呼び出すこともできます

string result = this.RenderRazorViewToString("ViewName", model);

これがあなたのコードをきれいに整頓するのに役立つことを願っています。


1
素敵な解決策!1つは、RenderRazorViewToStringは実際には拡張メソッドであるため(このキーワードでコントローラーパラメーターを渡すため)、この拡張メソッドは次のように呼び出すことができます。this.RenderRazorViewToString( "ViewName"、model);
セルゲイ

@Sergey Hmmm ...その方法で確認させてください。問題がなければ、回答を更新します。とにかくあなたの提案をありがとう。
Dilip0165

Dilip0165、var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext、viewName);でnull参照エラーが発生しました。何か考えはありますか?
CB4 2016年

@ CB4関数に渡す「viewName」の問題かもしれません。あなたはあなたのフォルダ構造に従ってフルパスで「viewName」を渡す必要があります。このことを確認してください。
Dilip0165

1
@Sergeyあなたの提案をありがとう、私はあなたの提案に従って私の答えを更新しました。それは完全に正しいです
Dilip0165

32

これは私にとってはうまくいきます:

public virtual string RenderView(ViewContext viewContext)
{
    var response = viewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;
    Stream filter = null;
    try
    {
        filter = new MemoryStream();
        response.Filter = filter;
        viewContext.View.Render(viewContext, viewContext.HttpContext.Response.Output);
        response.Flush();
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        if (filter != null)
        {
            filter.Dispose();
        }
        response.Filter = oldFilter;
    }
}

コメントありがとうございます。でも、ビュー内のレンダリングには使用されませんか?質問を更新したコンテキストでどのように使用できますか?
Dan Atkinson、

申し訳ありませんが、去年の最初のrcが0だった昨年のSilverlightについてはまだ考えています。(
ビューパス

これはまだRC1のリダイレクトを壊します
敗北

敗北:いいえ、そうではありません。もしそうなら、あなたは何か間違ったことをしています。
Dan Atkinson、

これをstackoverflow.com/questions/520863/…とマージし、ViewEnginesCollectionの認識を追加し、partialviewをポップしようとして、このstackoverflow.com/questions/520863/…を取得しました。:E
Arnis Lapsa 2009年

31

現在のHttpContextのResponseストリームを乱すことなく、ビューを文字列にレンダリングする新しいソリューションを見つけました(応答のContentTypeまたは他のヘッダーを変更することはできません)。

基本的には、ビュー自体をレンダリングするための偽のHttpContextを作成するだけです。

/// <summary>Renders a view to string.</summary>
public static string RenderViewToString(this Controller controller,
                                        string viewName, object viewData) {
    //Create memory writer
    var sb = new StringBuilder();
    var memWriter = new StringWriter(sb);

    //Create fake http context to render the view
    var fakeResponse = new HttpResponse(memWriter);
    var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse);
    var fakeControllerContext = new ControllerContext(
        new HttpContextWrapper(fakeContext),
        controller.ControllerContext.RouteData,
        controller.ControllerContext.Controller);

    var oldContext = HttpContext.Current;
    HttpContext.Current = fakeContext;

    //Use HtmlHelper to render partial view to fake context
    var html = new HtmlHelper(new ViewContext(fakeControllerContext,
        new FakeView(), new ViewDataDictionary(), new TempDataDictionary()),
        new ViewPage());
    html.RenderPartial(viewName, viewData);

    //Restore context
    HttpContext.Current = oldContext;    

    //Flush memory and return output
    memWriter.Flush();
    return sb.ToString();
}

/// <summary>Fake IView implementation used to instantiate an HtmlHelper.</summary>
public class FakeView : IView {
    #region IView Members

    public void Render(ViewContext viewContext, System.IO.TextWriter writer) {
        throw new NotImplementedException();
    }

    #endregion
}

これは、ASP.NET MVC 1.0で、ContentResult、JsonResultなどと共に動作します(元のHttpResponseでヘッダーを変更しても、「HTTPヘッダーが送信された後サーバーはコンテンツタイプを設定できません」という例外はスローされません)。

更新: ASP.NET MVC 2.0 RCではStringWriter、ビューをに書き込むために使用されるを渡す必要があるため、コードが少し変更されますViewContext

//...

//Use HtmlHelper to render partial view to fake context
var html = new HtmlHelper(
    new ViewContext(fakeControllerContext, new FakeView(),
        new ViewDataDictionary(), new TempDataDictionary(), memWriter),
    new ViewPage());
html.RenderPartial(viewName, viewData);

//...

HtmlHelperオブジェクトにRenderPartialメソッドはありません。これは不可能です-html.RenderPartial(viewName、viewData);
MartinF 2009

1
ASP.NET MVCリリース1.0には、いくつかのRenderPartial拡張メソッドがあります。特に私が使用しているものは、System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(このHtmlHelper、文字列、オブジェクト)です。このメソッドがMVCの最新のリビジョンに追加されていて、以前のバージョンには存在しなかったかどうかはわかりません。
LorenzCK 2009

ありがとう。System.Web.Mvc.Html名前空間をusing宣言に追加する必要があるだけです(そうでなければhtml.RenderPartial(..)にはもちろんアクセスできません:))
MartinF

誰かがこれをMVC2のRCで機能していますか?彼らはViewContextに追加のTextwriterパラメータを追加しました。新しいStringWriter()を追加しようとしただけですが、うまくいきませんでした。
beckelmw 2010年

1
@beckelmw:応答を更新しました。新しいインスタンスではなく、StringWriterへの書き込みに使用しているオリジナルを渡す必要があります。そうしないStringBuilderと、ビューの出力が失われます。
LorenzCK 2010年

11

この記事では、さまざまなシナリオでビューを文字列にレンダリングする方法について説明します。

  1. MVCコントローラーが別の独自のActionMethodを呼び出す
  2. 別のMVCコントローラーのActionMethodを呼び出すMVCコントローラー
  3. MVCコントローラーのActionMethodを呼び出すWebAPIコントローラー

ソリューション/コードはViewRendererと呼ばれるクラスとして提供されます。これは、GitHubにあるRick StahlのWestwindToolkitの一部です。

使用法(3.-WebAPIの例):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));

3
また、NuGetパッケージとしてWest Wind Web MVCユーティリティ(nuget.org/packages/Westwind.Web.Mvc)としても使用できます。おまけとして、ビューレンダラーは、部分的なビューだけでなく、レイアウトを含むビュー全体もレンダリングできます。コード付きブログ記事:weblog.west-wind.com/posts/2012/May/30/...
イェルーンK

これが小さなパッケージに分割されたら素晴らしいでしょう。Nugetパッケージはweb.configに多数の変更を加え、jsファイルをプロジェクトに追加します。これは、アンインストール時にクリーンアップされません:/
Josh Noe

8

MVCを完全に無視したい場合は、すべてのHttpContextの混乱を回避します...

using RazorEngine;
using RazorEngine.Templating; // For extension methods.

string razorText = System.IO.File.ReadAllText(razorTemplateFileLocation);
string emailBody = Engine.Razor.RunCompile(razorText, "templateKey", typeof(Model), model);

これは素晴らしいオープンソースのRazor Engineを使用します:https : //github.com/Antaris/RazorEngine


いいね!WebForms構文に同様の解析エンジンがあるかどうか知っていますか?まだいくつかの古いWebFormsビューがあり、まだRazorに移動できません。
Dan Atkinson

こんにちは、私はrazorengineに多くの問題があり、エラー報告はあまりよくありません。URLヘルパーがサポートされているとは思い
ません

@Layinkaこれが役立つかどうかはわかりませんが、エラー情報のほとんどはCompilerErrors例外のプロパティにあります。
Josh Noe 2017

5

あなたはこの方法を使用して文字列のビューを取得しています

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");

    if (model != null)
        ViewData.Model = model;

    using (StringWriter sw = new StringWriter())
    {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
    }
}

このメソッドを2つの方法で呼び出します

string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", null)

または

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)

4

ASP NET COREに関する追加のヒント:

インターフェース:

public interface IViewRenderer
{
  Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model);
}

実装:

public class ViewRenderer : IViewRenderer
{
  private readonly IRazorViewEngine viewEngine;

  public ViewRenderer(IRazorViewEngine viewEngine) => this.viewEngine = viewEngine;

  public async Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model)
  {
    ViewEngineResult viewEngineResult = this.viewEngine.FindView(controller.ControllerContext, name, false);

    if (!viewEngineResult.Success)
    {
      throw new InvalidOperationException(string.Format("Could not find view: {0}", name));
    }

    IView view = viewEngineResult.View;
    controller.ViewData.Model = model;

    await using var writer = new StringWriter();
    var viewContext = new ViewContext(
       controller.ControllerContext,
       view,
       controller.ViewData,
       controller.TempData,
       writer,
       new HtmlHelperOptions());

       await view.RenderAsync(viewContext);

       return writer.ToString();
  }
}

での登録 Startup.cs

...
 services.AddSingleton<IViewRenderer, ViewRenderer>();
...

そしてコントローラーでの使用:

public MyController: Controller
{
  private readonly IViewRenderer renderer;
  public MyController(IViewRendere renderer) => this.renderer = renderer;
  public async Task<IActionResult> MyViewTest
  {
    var view = await this.renderer.RenderAsync(this, "MyView", model);
    return new OkObjectResult(view);
  }
}

3

私はMVC 1.0 RTMを使用していますが、上記の解決策はどれもうまくいきませんでした。しかし、これはそうしました:

Public Function RenderView(ByVal viewContext As ViewContext) As String

    Dim html As String = ""

    Dim response As HttpResponse = HttpContext.Current.Response

    Using tempWriter As New System.IO.StringWriter()

        Dim privateMethod As MethodInfo = response.GetType().GetMethod("SwitchWriter", BindingFlags.NonPublic Or BindingFlags.Instance)

        Dim currentWriter As Object = privateMethod.Invoke(response, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, New Object() {tempWriter}, Nothing)

        Try
            viewContext.View.Render(viewContext, Nothing)
            html = tempWriter.ToString()
        Finally
            privateMethod.Invoke(response, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, New Object() {currentWriter}, Nothing)
        End Try

    End Using

    Return html

End Function

2

別のウェブサイトからMVC 3とRazorの実装を見ましたが、それは私にとってはうまくいきました:

    public static string RazorRender(Controller context, string DefaultAction)
    {
        string Cache = string.Empty;
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        System.IO.TextWriter tw = new System.IO.StringWriter(sb); 

        RazorView view_ = new RazorView(context.ControllerContext, DefaultAction, null, false, null);
        view_.Render(new ViewContext(context.ControllerContext, view_, new ViewDataDictionary(), new TempDataDictionary(), tw), tw);

        Cache = sb.ToString(); 

        return Cache;

    } 

    public static string RenderRazorViewToString(string viewName, object model)
    {

        ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
            var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
            viewResult.View.Render(viewContext, sw);
            return sw.GetStringBuilder().ToString();
        }
    } 

    public static class HtmlHelperExtensions
    {
        public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
        {
            ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName);

            if (result.View != null)
            {
                StringBuilder sb = new StringBuilder();
                using (StringWriter sw = new StringWriter(sb))
                {
                    using (HtmlTextWriter output = new HtmlTextWriter(sw))
                    {
                        ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output);
                        result.View.Render(viewContext, output);
                    }
                }
                return sb.ToString();
            } 

            return String.Empty;

        }

    }

Razorレンダーの詳細-MVC3 View Render to String


はい、これは実際には多かれ少なかれ受け入れられた回答のコピーです。:)
Dan Atkinson、

2

ControllerContextを渡さずにサービスレイヤーの文字列にビューをレンダリングするために、ここhttp://www.codemag.com/Article/1312081に、一般的なコントローラーを作成するRick Strahlの優れた記事があります。以下のコードの要約:

// Some Static Class
public static string RenderViewToString(ControllerContext context, string viewPath, object model = null, bool partial = false)
{
    // first find the ViewEngine for this view
    ViewEngineResult viewEngineResult = null;
    if (partial)
        viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
    else
        viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);

    if (viewEngineResult == null)
        throw new FileNotFoundException("View cannot be found.");

    // get the view and attach the model to view data
    var view = viewEngineResult.View;
    context.Controller.ViewData.Model = model;

    string result = null;

    using (var sw = new StringWriter())
    {
        var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
        view.Render(ctx, sw);
        result = sw.ToString();
    }

    return result;
}

// In the Service Class
public class GenericController : Controller
{ }

public static T CreateController<T>(RouteData routeData = null) where T : Controller, new()
{
    // create a disconnected controller instance
    T controller = new T();

    // get context wrapper from HttpContext if available
    HttpContextBase wrapper;
    if (System.Web.HttpContext.Current != null)
        wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
    else
        throw new InvalidOperationException("Cannot create Controller Context if no active HttpContext instance is available.");

    if (routeData == null)
        routeData = new RouteData();

    // add the controller routing if not existing
    if (!routeData.Values.ContainsKey("controller") &&
        !routeData.Values.ContainsKey("Controller"))
        routeData.Values.Add("controller", controller.GetType().Name.ToLower().Replace("controller", ""));

    controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
    return controller;
}

次に、Serviceクラスでビューをレンダリングします。

var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);

1

簡単なヒント

厳密に型指定されたモデルの場合は、RenderViewToStringに渡す前にViewData.Modelプロパティに追加するだけです。例えば

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);

0

より不明な質問から繰り返すには、MvcIntegrationTestFrameworkを見てください

結果をストリーミングするための独自のヘルパーを作成する手間を省き、十分に機能することが証明されています。私はこれがテストプロジェクトにあると思います。ボーナスとして、この設定を取得したら、他のテスト機能を利用できます。主な悩みは、おそらく依存チェーンを整理することでしょう。

 private static readonly string mvcAppPath = 
     Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory 
     + "\\..\\..\\..\\MyMvcApplication");
 private readonly AppHost appHost = new AppHost(mvcAppPath);

    [Test]
    public void Root_Url_Renders_Index_View()
    {
        appHost.SimulateBrowsingSession(browsingSession => {
            RequestResult result = browsingSession.ProcessRequest("");
            Assert.IsTrue(result.ResponseText.Contains("<!DOCTYPE html"));
        });
}

0

ASP.NETCore RC2でこれを行うために私が書いたクラスは次のとおりです。Razorを使用してhtmlメールを生成できるように、それを使用しています。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using System.IO;
using System.Threading.Tasks;

namespace cloudscribe.Web.Common.Razor
{
    /// <summary>
    /// the goal of this class is to provide an easy way to produce an html string using 
    /// Razor templates and models, for use in generating html email.
    /// </summary>
    public class ViewRenderer
    {
        public ViewRenderer(
            ICompositeViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IHttpContextAccessor contextAccesor)
        {
            this.viewEngine = viewEngine;
            this.tempDataProvider = tempDataProvider;
            this.contextAccesor = contextAccesor;
        }

        private ICompositeViewEngine viewEngine;
        private ITempDataProvider tempDataProvider;
        private IHttpContextAccessor contextAccesor;

        public async Task<string> RenderViewAsString<TModel>(string viewName, TModel model)
        {

            var viewData = new ViewDataDictionary<TModel>(
                        metadataProvider: new EmptyModelMetadataProvider(),
                        modelState: new ModelStateDictionary())
            {
                Model = model
            };

            var actionContext = new ActionContext(contextAccesor.HttpContext, new RouteData(), new ActionDescriptor());
            var tempData = new TempDataDictionary(contextAccesor.HttpContext, tempDataProvider);

            using (StringWriter output = new StringWriter())
            {

                ViewEngineResult viewResult = viewEngine.FindView(actionContext, viewName, true);

                ViewContext viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewData,
                    tempData,
                    output,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

                return output.GetStringBuilder().ToString();
            }
        }
    }
}

0

上記の方法でエラーが発生したときにかみそりビューページをレンダリングするより良い方法を見つけました。このソリューションは、Webフォーム環境とmvc環境の両方に対応しています。コントローラーは必要ありません。

これがコード例です。この例では、非同期httpハンドラーを使用してmvcアクションをシミュレートしました。

    /// <summary>
    /// Enables processing of HTTP Web requests asynchronously by a custom HttpHandler that implements the IHttpHandler interface.
    /// </summary>
    /// <param name="context">An HttpContext object that provides references to the intrinsic server objects.</param>
    /// <returns>The task to complete the http request.</returns>
    protected override async Task ProcessRequestAsync(HttpContext context)
    {
        if (this._view == null)
        {
            this.OnError(context, new FileNotFoundException("Can not find the mvc view file.".Localize()));
            return;
        }
        object model = await this.LoadModelAsync(context);
        WebPageBase page = WebPageBase.CreateInstanceFromVirtualPath(this._view.VirtualPath);
        using (StringWriter sw = new StringWriter())
        {
            page.ExecutePageHierarchy(new WebPageContext(new HttpContextWrapper(context), page, model), sw);
            await context.Response.Output.WriteAsync(sw.GetStringBuilder().ToString());
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.