簡體   English   中英

在 ASP.NET Core 中將 Razor 視圖渲染為字符串

[英]Render Razor View to string in ASP.NET Core

我使用RazorEngine在我的 MVC 6 項目中解析模板,如下所示:

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

它適用於 beta 6。升級到 beta 7 后它無法正常工作,並出現以下錯誤:

MissingMethodException:找不到方法:“無效 Microsoft.AspNet.Razor.CodeGenerators.GeneratedClassContext.set_ResolveUrlMethodName(System.String)”。 在 RazorEngine.Compilation.CompilerServiceBase.CreateHost(類型模板類型,類型模型類型,字符串類名)

這是 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 中還有另一種解析模板的方法嗎?

2016 年 7 月更新

在以下版本1.0.0 , RC2上工作正常


誰以 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 中的鏈接呈現為相對URL,因此這不適用於外部視圖(如電子郵件等)。

至於現在在控制器上生成鏈接並通過 ViewModel 將其傳遞給視圖。

信用

源代碼摘自(感謝@pholly): https ://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs)

我發現這個討論它的線程: https ://github.com/aspnet/Mvc/issues/3091

線程中的某個人在這里創建了一個示例服務: https ://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

經過反復試驗,我能夠減少服務,因此它只需要一個有效的HttpContext和一個ViewEngine並且我添加了一個不需要模型的重載。 視圖相對於您的應用程序根目錄(它們不必位於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;
        }
    }
}

過去,我在類庫中使用過RazorEngine ,因為我的目標是在這個類庫中呈現模板。

據我了解,您似乎在 MVC 6.0 項目中,所以為什么不使用RenderPartialViewToString()方法而不必添加對RazorEngine的依賴項?

請記住,我只是問,因為我很好奇。

例如,在 VS2015 中,我創建了一個新的 ASP.NET Web 應用程序,並從 ASP.NET 5 Preview Templates 中選擇了 Web Application 模板。

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();
    }
}

歸功於@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>

最后一步是讓我的Controller繼承自我的BaseController

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("test@test.com", "registration", emailbody);

我不確定這是否適合您當前的任務。

今天我已經完成了可以解決您問題的庫。 您可以在 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

為了改進@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);
        }
    }
}

ResolveUrlMethodName已刪除。 因此,在您的CreateHost您試圖設置一個不存在的屬性:)。

我們決定將~/處理從核心 Razor 移動到在Microsoft.AspNet.Mvc.Razor程序集中實現的TagHelper 這是對刪除該方法的位的提交

希望這會有所幫助。

將部分視圖轉換為字符串響應的擴展方法。

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)
}

.NET 5 實施

public static async Task<string> ViewToString(this PartialViewResult partialView, Controller controller)
    {
        using (var writer = new StringWriter())
        {
            var services = controller.ControllerContext.HttpContext.RequestServices;
            var viewEngine = services.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
            var viewName = partialView.ViewName ?? controller.ControllerContext.ActionDescriptor.ActionName;
            var view = viewEngine.FindView(controller.ControllerContext, viewName, false).View;
            var viewContext = new ViewContext(controller.ControllerContext, view, partialView.ViewData, partialView.TempData, writer, new HtmlHelperOptions());
            await view.RenderAsync(viewContext);
            return writer.ToString();
        }
    }

可以在此處找到僅使用 ASP.NET Core、沒有外部庫和反射的替代解決方案: https ://weblogs.asp.net/ricardoperes/getting-html-for-a-viewresult-in-asp-net- 核心 它只需要一個ViewResult和一個HttpContext

這個想法是獲取一個ViewResult並調用一些方法,比如ToHtml ,以獲取渲染的輸出。 此方法可能如下所示:

public static class ViewResultExtensions {
    public static string ToHtml(this ViewResult result, HttpContext httpContext) {            
        var feature = httpContext.Features.Get<IRoutingFeature>();
        var routeData = feature.RouteData;
        var viewName = result.ViewName ?? routeData.Values["action"] as string;
        var actionContext = new ActionContext(httpContext, routeData, new ControllerActionDescriptor());
        var options = httpContext.RequestServices.GetRequiredService<IOptions<MvcViewOptions>>();
        var htmlHelperOptions = options.Value.HtmlHelperOptions;
        var viewEngineResult = result.ViewEngine?.FindView(actionContext, viewName, true) ?? options.Value.ViewEngines.Select(x => x.FindView(actionContext, viewName, true)).FirstOrDefault(x => x != null);
        var view = viewEngineResult.View;
        var builder = new StringBuilder();

        using (var output = new StringWriter(builder)) {
            var viewContext = new ViewContext(actionContext, view, result.ViewData, result.TempData, output, htmlHelperOptions);

            view
                .RenderAsync(viewContext)
                .GetAwaiter()
                .GetResult();
        }
        return builder.ToString();
    }
}

要使用它,只需執行以下操作:

var view = this.View(“ViewName”);
var html = view.ToHtml();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM