web-dev-qa-db-ja.com

RazorビューをASP.NET Coreの文字列にレンダリングします

私は RazorEngine を使用して、MVC 6プロジェクトのテンプレートを次のように解析します。

Engine.Razor.RunCompile(File.ReadAllText(fullTemplateFilePath), templateName, null, model);

ベータ6で正常に動作します。ベータ7にアップグレードした後、エラーで動作しません。

MissingMethodException:メソッドが見つかりません: "Void Microsoft.AspNet.Razor.CodeGenerators.GeneratedClassContext.set_ResolveUrlMethodName(System.String)"。 RazorEngine.Compilation.CompilerServiceBase.CreateHost(Type templateType、Type modelType、String className)内

これはglobal.jsonです:

{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-beta7",
    "runtime": "clr",
    "architecture": "x64"
  }
}

これはproject.jsonです:

...
"dependencies": {
    "EntityFramework.SqlServer": "7.0.0-beta7",
    "EntityFramework.Commands": "7.0.0-beta7",
    "Microsoft.AspNet.Mvc": "6.0.0-beta7",
    "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta7",
    "Microsoft.AspNet.Authentication.Cookies": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.Facebook": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.Google": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.MicrosoftAccount": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.Twitter": "1.0.0-beta7",
    "Microsoft.AspNet.Diagnostics": "1.0.0-beta7",
    "Microsoft.AspNet.Diagnostics.Entity": "7.0.0-beta7",
    "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta7",
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta7",
    "Microsoft.AspNet.Server.WebListener": "1.0.0-beta7",
    "Microsoft.AspNet.StaticFiles": "1.0.0-beta7",
    "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.Abstractions": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.UserSecrets": "1.0.0-beta7",
    "Microsoft.Framework.Logging": "1.0.0-beta7",
    "Microsoft.Framework.Logging.Console": "1.0.0-beta7",
    "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-beta7",
    "RazorEngine": "4.2.2-beta1"
  },
...
  "frameworks": {
    "dnx451": { }
  },
...

私のテンプレートは:

@model dynamic
@{
    Layout = null;
}

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Registration</title>
</head>
<body>
<p>
    Hello, @Model
</p>
</body>
</html>

誰にも同様の問題がありますか? MVC 6でテンプレートを解析する別の方法はありますか?

26
hcp

2016年7月更新

次のバージョンで正常に動作します1.0.0RC2


誰がaspnetcore RC2をターゲットにしているのか、このスニペットはあなたを助けるかもしれません:

  • 別のサービスを作成して、コントローラーコンテキストにない場合でも使用できるようにします。コマンドラインまたはキューランナーなどから...
  • このサービスをStartupクラスのIoCコンテナーに登録します

https://Gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09

使用法

// using a Model
string html = view.Render("Emails/Test", new Product("Apple"));

// using a Dictionary<string, object>
var viewData = new Dictionary<string, object>();
viewData["Name"] = "123456";

string html = view.Render("Emails/Test", viewData);

ノート

RazorのリンクはrelativeURLとしてレンダリングされるため、外部ビュー(電子メールなど)では機能しません。

今のところ、コントローラーでリンクを生成し、ViewModelを介してビューに渡します。

クレジット

ソースは(Thanks To @pholly)から抽出されます: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

24
amd

私はそれを議論するこのスレッドを見つけました: https://github.com/aspnet/Mvc/issues/3091

スレッドの誰かがサンプルサービスを作成しました: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

試行錯誤の後、有効なHttpContextViewEngineのみが必要になるようにサービスを削減することができ、モデルを必要としないオーバーロードを追加しました。ビューはアプリケーションのルートに相対的です(Viewsフォルダーに存在する必要はありません)。

Startup.csにサービスを登録し、HttpContextAccessorも登録する必要があります。

//Startup.cs ConfigureServices()
services.AddTransient<ViewRenderService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.IO;

namespace LibraryApi.Services
{
    public class ViewRenderService
    {
        IRazorViewEngine _viewEngine;
        IHttpContextAccessor _httpContextAccessor;

        public ViewRenderService(IRazorViewEngine viewEngine, IHttpContextAccessor httpContextAccessor)
        {
            _viewEngine = viewEngine;
            _httpContextAccessor = httpContextAccessor;
        }

        public string Render(string viewPath)
        {
            return Render(viewPath, string.Empty);
        }

        public string Render<TModel>(string viewPath, TModel model)
        {
            var viewEngineResult = _viewEngine.GetView("~/", viewPath, false);

            if (!viewEngineResult.Success)
            {
                throw new InvalidOperationException($"Couldn't find view {viewPath}");
            }

            var view = viewEngineResult.View;

            using (var output = new StringWriter())
            {
                var viewContext = new ViewContext();
                viewContext.HttpContext = _httpContextAccessor.HttpContext;
                viewContext.ViewData = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                { Model = model };
                viewContext.Writer = output;

                view.RenderAsync(viewContext).GetAwaiter().GetResult();

                return output.ToString();
            }
        }
    }
}

使用例:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using LibraryApi.Services;
using System.Dynamic;

namespace LibraryApi.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        ILogger<ValuesController> _logger;
        ViewRenderService _viewRender;
        public ValuesController(ILogger<ValuesController> logger, ViewRenderService viewRender)
        {
            _logger = logger;
            _viewRender = viewRender;
        }

        // GET api/values
        [HttpGet]
        public string Get()
        {
            //ViewModel is of type dynamic - just for testing
            dynamic x = new ExpandoObject();
            x.Test = "Yes";
            var viewWithViewModel = _viewRender.Render("eNotify/Confirm.cshtml", x);
            var viewWithoutViewModel = _viewRender.Render("MyFeature/Test.cshtml");
            return viewWithViewModel + viewWithoutViewModel;
        }
    }
}
18
pholly

以前は、このクラスライブラリ内からテンプレートをレンダリングすることが目標だったため、クラスライブラリ内でRazorEngineを使用しました。

私の理解では、あなたはMVC 6.0プロジェクト内にいるようですので、RazorEngineへの依存関係を追加せずにRenderPartialViewToString()メソッドを使用してみませんか?

覚えておいてください、私は好奇心が強いので、私は尋ねているだけです。

例として、VS2015内から新しいASP.NET Webアプリケーションを作成し、ASP.NET 5プレビューテンプレートからWebアプリケーションテンプレートを選択しました。

ViewModelsフォルダー内に、PersonViewModelを作成しました。

_public class PersonViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", this.FirstName, this.LastName);
        }
    } 
}
_

次に、新しいBaseControllerを作成し、RenderPartialViewToString()メソッドを追加しました。

_public string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ActionContext.ActionDescriptor.Name;

    ViewData.Model = model;

    using (StringWriter sw = new StringWriter())
    {
        var engine = Resolver.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
        ViewEngineResult viewResult = engine.FindPartialView(ActionContext, viewName);

        ViewContext viewContext = new ViewContext(ActionContext, viewResult.View, ViewData, TempData, sw,new HtmlHelperOptions());

        var t = viewResult.View.RenderAsync(viewContext);
        t.Wait();

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

彼の method の功績は@DavidGにある

_Views-->Shared_フォルダー内に新しいTemplatesフォルダーを作成し、その中に単純な_RegistrationTemplate.cshtml_ビューを強く入力しましたPersonViewModelに次のようにします:

_@model MyWebProject.ViewModels.PersonViewModel
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Registration</title>
</head>
<body>
    <p>
        Hello, @Model.FullName
    </p>
</body>
</html>
_

最後のステップは、ControllerBaseControllerから継承することです

_public class MyController : BaseController
_

次のようなものを作成します。

_public IActionResult Index()
{
    var model = new PersonViewModel();
    model.FirstName = "Frank";
    model.LastName = "Underwood";
    var emailbody = base.RenderPartialViewToString("Templates/RegistrationTemplate", model);

    return View();
}
_

もちろん、変数emailbodyについては何もしないので、上記の例は役に立ちませんが、アイデアはそれがどのように使用されるかを示すことです。

この時点で、(たとえば)EmailServiceを呼び出してemailbodyを渡すことができます。

__emailService.SendEmailAsync("[email protected]", "registration", emailbody);
_

これが現在のタスクに適した選択肢かどうかはわかりません。

14
Vlince

今日、あなたの問題を解決できるライブラリを完成させました。 ASP.NETには依存関係がないため、ASP.NETから使用できます。

例:

string content = "Hello @Model.Name. Welcome to @Model.Title repository";

var model = new
{
  Name = "John Doe",
  Title = "RazorLight"
};

var engine = new RazorLightEngine();
string result = engine.ParseString(content, model);

//Output: Hello John Doe, Welcome to RazorLight repository

詳細: https://github.com/toddams/RazorLight

12
Toddams

@vlinceの答えを改善するために(それは私にとっては箱から出していない)、ここに私がやったことがあります:

1-他のコントローラーが継承するベースコントローラーを作成する

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.IO;

namespace YourNameSpace
{
    public class BaseController : Controller
    {
        protected ICompositeViewEngine viewEngine;

        public BaseController(ICompositeViewEngine viewEngine)
        {
            this.viewEngine = viewEngine;
        }

        protected string RenderViewAsString(object model, string viewName = null)
        {
            viewName = viewName ?? ControllerContext.ActionDescriptor.ActionName;
            ViewData.Model = model;

            using (StringWriter sw = new StringWriter())
            {
                IView view = viewEngine.FindView(ControllerContext, viewName, true).View;
                ViewContext viewContext = new ViewContext(ControllerContext, view, ViewData, TempData, sw, new HtmlHelperOptions());

                view.RenderAsync(viewContext).Wait();

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

2-ベースコントローラーを継承し、メソッドを呼び出す

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;

namespace YourNameSpace
{
    public class YourController : BaseController
    {
        public YourController(ICompositeViewEngine viewEngine) : base(viewEngine) { }

        public string Index(int? id)
        {
            var model = new MyModel { Name = "My Name" };

            return RenderViewAsString(model);
        }
    }
}
4
Gudradain

ResolveUrlMethodNameは削除されました。したがって、CreateHosthere で、存在しないプロパティを設定しようとしています:)。

~/処理をコアRazorからMicrosoft.AspNet.Mvc.Razorアセンブリに実装されたTagHelperに移動することにしました。メソッドを削除したビットの commit を次に示します。

これがお役に立てば幸いです。

3

ASP.NET Coreのみを使用し、外部ライブラリもリフレクションも使用しない代替ソリューションは、ここにあります: https://weblogs.asp.net/ricardoperes/getting-html-for-a-viewresult-in- asp-net-core 。 ViewResultとHttpContextが必要です。

1
Ricardo Peres

部分ビューを文字列応答に変換するための拡張メソッド。

public static class PartialViewToString
{
    public static async Task<string> ToString(this PartialViewResult partialView, ActionContext actionContext)
    {
        using(var writer = new StringWriter())
        {
            var services = actionContext.HttpContext.RequestServices;
            var executor = services.GetRequiredService<PartialViewResultExecutor>();
            var view = executor.FindView(actionContext, partialView).View;
            var viewContext = new ViewContext(actionContext, view, partialView.ViewData, partialView.TempData, writer, new HtmlHelperOptions());
            await view.RenderAsync(viewContext);
            return writer.ToString();
        }
    }
}

コントローラーアクションでの使用。

public async Task<IActionResult> Index()
{
    return await PartialView().ToString(ControllerContext)
}
1