web-dev-qa-db-ja.com

Razorページを文字列にレンダリングする

問題:

Razor Pageの一部を文字列にレンダリングする必要があります。

なぜこれが欲しいのですか?

部分的なビューとその他のオプションのパラメーターを含むJSONで応答するコントローラーアクションを作成します。

試み:

ビューを文字列にレンダリングする次の例に精通しています: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

ただし、Viewsディレクトリのみを検索するため、Pagesとは互換性がないため、パーシャルへの絶対パスを指定した場合でも、_Layout.cshtmlを見つけようとします(これもできません!)それを見つけるために。

ページをレンダリングするように変更しようとしましたが、レンダリングしようとすると、パーシャルでViewDataのNullReferenceExceptionが発生します。 NullViewに関係しているのではないかと思いますが、代わりに何を配置するのかわかりません(RazorViewのコンストラクターには、正しく取得する方法がわからない多くのオブジェクトが必要です)。

コード:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0: https://www.Apache.org/licenses/LICENSE-2.0
// Modified by OronDF343: Uses pages instead of views.

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

namespace TestAspNetCore.Services
{
    public class RazorPageToStringRenderer
    {
        private readonly IRazorViewEngine _viewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;

        public RazorPageToStringRenderer(
            IRazorViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            _viewEngine = viewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        }

        public async Task<string> RenderPageToStringAsync<TModel>(string viewName, TModel model)
        {
            var actionContext = GetActionContext();
            var page = FindPage(actionContext, viewName);

            using (var output = new StringWriter())
            {
                var viewContext = new ViewContext(actionContext,
                                                  new NullView(),
                                                  new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(),
                                                                                 new ModelStateDictionary())
                                                  {
                                                      Model = model
                                                  },
                                                  new TempDataDictionary(actionContext.HttpContext,
                                                                         _tempDataProvider),
                                                  output,
                                                  new HtmlHelperOptions());

                page.ViewContext = viewContext;
                await page.ExecuteAsync();

                return output.ToString();
            }
        }

        private IRazorPage FindPage(ActionContext actionContext, string pageName)
        {
            var getPageResult = _viewEngine.GetPage(executingFilePath: null, pagePath: pageName);
            if (getPageResult.Page != null)
            {
                return getPageResult.Page;
            }

            var findPageResult = _viewEngine.FindPage(actionContext, pageName);
            if (findPageResult.Page != null)
            {
                return findPageResult.Page;
            }

            var searchedLocations = getPageResult.SearchedLocations.Concat(findPageResult.SearchedLocations);
            var errorMessage = string.Join(
                Environment.NewLine,
                new[] { $"Unable to find page '{pageName}'. The following locations were searched:" }.Concat(searchedLocations));

            throw new InvalidOperationException(errorMessage);
        }

        private ActionContext GetActionContext()
        {
            var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
            return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        }
    }
}
16
OronDF343

これが私がやった方法です。

いつものように、サービスをStartup.csに登録します

services.AddScoped<IViewRenderService, ViewRenderService>();

サービスは次のように定義されます。

public interface IViewRenderService
{
    Task<string> RenderToStringAsync<T>(string viewName, T model) where T : PageModel;
}

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly IHttpContextAccessor _httpContext;
    private readonly IActionContextAccessor _actionContext;
    private readonly IRazorPageActivator _activator;


    public ViewRenderService(IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider,
        IHttpContextAccessor httpContext,
        IRazorPageActivator activator,
        IActionContextAccessor actionContext)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;

        _httpContext = httpContext;
        _actionContext = actionContext;
        _activator = activator;

    }


    public async Task<string> RenderToStringAsync<T>(string pageName, T model) where T : PageModel
    {


        var actionContext =
            new ActionContext(
                _httpContext.HttpContext,
                _httpContext.HttpContext.GetRouteData(),
                _actionContext.ActionContext.ActionDescriptor
            );

        using (var sw = new StringWriter())
        {
            var result = _razorViewEngine.FindPage(actionContext, pageName);

            if (result.Page == null)
            {
                throw new ArgumentNullException($"The page {pageName} cannot be found.");
            }

            var view = new RazorView(_razorViewEngine,
                _activator,
                new List<IRazorPage>(),
                result.Page,
                HtmlEncoder.Default,
                new DiagnosticListener("ViewRenderService"));


            var viewContext = new ViewContext(
                actionContext,
                view,
                new ViewDataDictionary<T>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                },
                new TempDataDictionary(
                    _httpContext.HttpContext,
                    _tempDataProvider
                ),
                sw,
                new HtmlHelperOptions()
            );


            var page = ((Page)result.Page);

            page.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext
            {
                ViewData = viewContext.ViewData

            };

            page.ViewContext = viewContext;


            _activator.Activate(page, viewContext);

            await page.ExecuteAsync();


            return sw.ToString();
        }
    }



}

このように呼んでいます

  emailView.Body = await this._viewRenderService.RenderToStringAsync("Email/ConfirmAccount", new Email.ConfirmAccountModel
                {
                    EmailView = emailView,
                });

「Email/ConfirmAccount」は私のRazorページ(ページの下)へのパスです。 「ConfirmAccountModel」は、そのページの私のページモデルです。

PageContextの設定時にページのViewDataが設定されるため、ViewDataはnullです。これが設定されていない場合、ViewDataはnullです。

私も電話しなければならないことに気づきました

_activator.Activate(page, viewContext);

それがすべて機能するために。これはまだ完全にはテストされていないため、すべてのシナリオで機能するわけではありませんが、始めるのに役立ちます。

11
Foxster

私も同じ問題を抱えていました。

RazorViewEngineのソースコードを調べたところ、「ページ」ルートデータを使用してページが検索されていることがわかりました。

var routeData = new RouteData();
routeData.Values.Add("page", "/Folder/MyPage");

RouteDataのフルパス "/ Folder/MyPage"とGetPage呼び出しのページ名 "MyPage"を使用すると、うまくいきます。

2
William Fink

私のように、__httpContext.HttpContext_からGetRouteData()を取得できず、__actionContext_がnullの場合は、拡張機能を作成できます。

_using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace Utils
{
    public static class PageExtensions
    {
        public static async Task<string> RenderViewAsync(this PageModel pageModel, string pageName)
        {
            var actionContext = new ActionContext(
                pageModel.HttpContext,
                pageModel.RouteData,
                pageModel.PageContext.ActionDescriptor
            );

            using (var sw = new StringWriter())
            {
                IRazorViewEngine _razorViewEngine = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorViewEngine)) as IRazorViewEngine;
                IRazorPageActivator _activator = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorPageActivator)) as IRazorPageActivator;

                var result = _razorViewEngine.FindPage(actionContext, pageName);

                if (result.Page == null)
                {
                    throw new ArgumentNullException($"The page {pageName} cannot be found.");
                }

                var page = result.Page;

                var view = new RazorView(_razorViewEngine,
                    _activator,
                    new List<IRazorPage>(),
                    page,
                    HtmlEncoder.Default,
                    new DiagnosticListener("ViewRenderService"));


                var viewContext = new ViewContext(
                    actionContext,
                    view,
                    pageModel.ViewData,
                    pageModel.TempData,
                    sw,
                    new HtmlHelperOptions()
                );


                var pageNormal = ((Page)result.Page);

                pageNormal.PageContext = pageModel.PageContext;

                pageNormal.ViewContext = viewContext;


                _activator.Activate(pageNormal, viewContext);

                await page.ExecuteAsync();

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

注:このコードは、呼び出されるページのみをレンダリングし、レイアウトを省略します。

次のようにPageModelから呼び出すだけです。

var s = this.RenderViewAsync("sendEmail").Result;

_"sendEmail"_は_PageModel view_の名前で、パスは_/Pages/sendEmail.cshtml_です

2
user3502626

これが私が下ったルートです。非常にシンプルで扱いやすい...

using System;
using System.IO;
using System.Net;

namespace gMIS.Rendering
{
    public static class RazorPage
    {
        public static string RenderToString(string url)
        {
            try
            {
                //Grab page
                WebRequest request = WebRequest.Create(url);
                WebResponse response = request.GetResponse();
                Stream data = response.GetResponseStream();
                string html = String.Empty;
                using (StreamReader sr = new StreamReader(data))
                {
                    html = sr.ReadToEnd();
                }
                return html;
            }
            catch (Exception err)
            {
                return {Handle as you see fit};
            }
        }
    }
}

そのように呼ばれる....

var msg = RazorPage.RenderToString(url);

例:

var pathToRazorPageFolder = request.PathToRazorPageFolder();

var msg = RazorPage.RenderToString($"{pathToRazorPageFolder}/Task_Summary?userGuid={userGuid}&taskId={task.Task_ID}&includelink=true&linkuserGuid={linkUserGuid}");

上記の例では、アプリの基本パスを取得するために作成したこの拡張機能を使用しています。

namespace Microsoft.AspNetCore.Http
{
    public static class RequestExtension
    {
        public static string PathToRazorPageFolder(this HttpRequest request)
        {
            if (request != null) {
                var requestPath = request.Path.ToString();
                var returnPathToFolder = request.Scheme + "://" + request.Host + requestPath.Substring(0, requestPath.LastIndexOf("/")); ;
                return returnPathToFolder;
            } else
            {
                return "HttpRequest was null";
            }
        }
    }
}

これは依存性注入を使用しないことを知っていますが、それは簡単です。そして、それはうまくいきます。また、どのようにホストされていても、どのページでも機能します。そのページがアプリケーションの内部でも外部でもかまいません。

1
Mark Brookes