在 ASP.NET Core 中将 Razor 视图渲染为字符串
Posted
技术标签:
【中文标题】在 ASP.NET Core 中将 Razor 视图渲染为字符串【英文标题】:Render Razor View to string in ASP.NET Core 【发布时间】:2015-09-14 07:05:37 【问题描述】:我在 MVC 6 项目中使用 RazorEngine 解析模板,如下所示:
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(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 中还有另一种解析模板的方法吗?
【问题讨论】:
我没有读过这个问题,但投票赞成答案:) 【参考方案1】:2016 年 7 月更新
在以下版本1.0.0
、RC2
上工作正常
谁在瞄准 aspnetcore RC2,这个 sn-p 可能会帮助你:
创建一个单独的服务,以便在您不在控制器上下文中时使用它,例如从命令行或队列运行器等... 在您的 IoC 容器中的Startup
类中注册此服务
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)
【讨论】:
Gist 与 github.com/aspnet/Entropy/blob/dev/samples/… 完全相同的代码是因为 github.com/aspnet/Mvc/issues/3091 而创建的 如果我在视图中使用链接 taghelper,我将无法正常工作:<a asp-controller="Whatever" asp-action="DoSomething" asp-route-thingy="ThingyValue">link text</a>
。
@BenCollins,我假设你得到了一个错误的 URL(相对 URL),它在电子邮件等外部链接上不起作用,对吗?
@Ahmad 不,问题是链接 taghelper 需要更多上下文才能工作。链接 taghelper 依赖于UrlHelper
,而UrlHelper
需要真正的路由数据,否则会抛出异常。对我来说幸运的是,我在 MVC 应用程序中执行此操作,因此我可以使用 IHttpContextAccessor
构建具有实际值的 ActionContext
是的,我认为这也是我的问题,因为我需要在IUrlHelper
中添加扩展方法AbsoluteAction,很高兴您找到了解决方案:)
【参考方案2】:
我找到了讨论它的线程: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;
【讨论】:
谢谢 - 与 ASP.NET Core 1.0.1 配合使用非常好。喜欢一般/服务方法:) 感谢@pholly,这也适用于 Core 1.1。它是一个非常好的解决问题的方法。有什么想法需要考虑 _ViewImports.cshtml、_ViewStart.cshtml 和 _Layout.cshtml,包括 _Layout.cshtml 中使用的 RenderBody 和 RenderSection 命令?我尝试将它们放在与正在加载的视图相同的文件夹中,但它们没有被使用,至少基于结果输出。 @TommyBaggett 我的代码基于此:github.com/aspnet/Entropy/blob/dev/samples/… 所以我不能相信 :) 我没有意识到_Layout.cshtml
并且不会使用这样的 - 也许是 @987654336毕竟需要@来加载那些?试试我链接的服务,看看它是否有效。它使用ActionContext
。
谢谢@pholly,我试了一下,发现我的观点时遇到了问题。按照下面另一个答案中的建议,我尝试了 RazorLight 引擎 (github.com/toddams/RazorLight)。它工作得很好。
使用 ASP.NET Core 1.1.0 时,我遇到了一个问题,当我在这里开始使用该版本时该问题消失了:github.com/aspnet/Entropy/blob/dev/samples/…。在采用示例中的方法之前,我的错误的调用堆栈与此非常相似:github.com/aspnet/Mvc/issues/5505【参考方案3】:
过去,我在类库中使用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 的method
在Views-->Shared
文件夹内,我创建了一个新的Templates 文件夹,我在其中添加了一个简单的RegistrationTemplate.cshtml
View strongly typed 到我的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);
我不确定这是否适合您当前的任务。
【讨论】:
如何在类库中做同样的事情?你那里没有 ActionContext... 上面的例子展示了如何将视图渲染为字符串。该示例位于所有依赖项都已存在的 asp.net MVC 应用程序中。如果您打算在类库中使用类似的东西,则上述方法可能不是最好的方法,因为您不会拥有所有必要的依赖关系,什么都没有……为此,您可能想尝试一种不同的方法,例如这个:strathweb.com/2016/01/…【参考方案4】:今天我已经完成了可以解决您问题的库。您可以在 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
【讨论】:
感谢您提供 RazorLight 库。只要您解析单个视图文件,这里提供的其他一些解决方案就可以工作。当我需要考虑支持 _ViewStart 和 _Layout 文件时,我切换到了您的库。您的库非常适合我的需求。 我很难让其他解决方案发挥作用。但这很好用! @toddams 你能帮我解决我打开的问题吗,我们正在尝试使用该库从动态模型问题https://github.com/toddams/RazorLight/issues/158
生成动态模板
很好的解决方案。干杯。【参考方案5】:
为了改进@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);
【讨论】:
谢谢 - 与 ASP.NET Core 1.0.1 配合使用非常好 TutorialController 实际上应该是示例中的 YourController 吗?否则,非常感谢这个想法。在使用 BaseForm 的旧 ASP.NET 表单时代,我或多或少已经忘记了这种技术 :)【参考方案6】:ResolveUrlMethodName
已被删除。因此,在您的 CreateHost
here 中,您试图设置一个不存在的属性:)。
我们决定将 ~/
处理从核心 Razor 转移到在 Microsoft.AspNet.Mvc.Razor
程序集中实现的 TagHelper
。这是删除该方法的位的commit。
希望这会有所帮助。
【讨论】:
感谢详细的解释!以后我会看的。【参考方案7】:可以在此处找到仅使用 ASP.NET Core、没有外部库和反射的替代解决方案:https://weblogs.asp.net/ricardoperes/getting-html-for-a-viewresult-in-asp-net-core。 它只需要一个 ViewResult 和一个 HttpContext。
【讨论】:
【参考方案8】:将部分视图转换为字符串响应的扩展方法。
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();
【讨论】:
这似乎不再适用于 .NET 5。我不知道如何注册 PartialViewResultExecutor。 :(No service for type 'Microsoft.AspNetCore.Mvc.ViewFeatures.PartialViewResultExecutor' has been registered.
以上是关于在 ASP.NET Core 中将 Razor 视图渲染为字符串的主要内容,如果未能解决你的问题,请参考以下文章
覆盖 asp.net core razor 页面中的 razor 视图