我正在使用新的ASP.Net 5 / MVC 6库构建一个项目,我来到了一个我希望将一个剃刀页面动态渲染为字符串的部分。
这适用于动态窗口小部件系统,因此我们的想法是在生成模型之后但在渲染主视图之前在ActionFilter
中执行此操作。
它的设置非常大,但它主要需要服务并将各个部分组合在一起(如果有人知道更简单的方法,请告诉我们!)。
public override void OnActionExecuted(ActionExecutedContext context) {
var pageFactory = (IRazorPageFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorPageFactory));
var viewEngine = (IRazorViewEngine)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewEngine));
var viewFactory = (IRazorViewFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewFactory));
var modelMetadataProvider = (IModelMetadataProvider)context.HttpContext.ApplicationServices.GetService(typeof(IModelMetadataProvider));
var page = pageFactory.CreateInstance($"~/Views/Widgets/{widgetInfo.Type}.cshtml");
var view = viewFactory.GetView(viewEngine, page, false);
var sb = new StringBuilder();
var sw = new StringWriter(sb);
page.ViewContext = new ViewContext(new ActionContext(context.HttpContext, context.RouteData, context.ActionDescriptor), view, new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()), sw);
设置完所有后,我尝试在我的ViewData上设置Model
- 属性,我可以看到我设置了正确的对象,并且页面的类型正确。
page.ViewContext.ViewData.Model = widgetInfo.Setting;
然后,当我在模板中没有使用任何模型时,我会执行效果很好的页面。
page.ExecuteAsync().GetAwaiter().GetResult();
sw.Flush();
var renderedWidget = sb.ToString();
我可以看到我的模板已加载(甚至在其中放置了断点),但Model始终为null。目前我使用这个简单的模板。
@model DataObjects.CodeWidgetModel
@Html.Raw(Model.Code)
我一直在GitHub上搜索MVC6-sources,看看我能错过什么,但找不到任何有用的东西。
我正在运行ASP.Net MVC 6.0.0-beta3-12628
和KRE 1.0.0-beta3-11014
,但很可能会尽快升级到beta4。
任何人都知道为什么在Model
上设置ViewData
是不够的,或者我需要做些什么才能使其正常工作?
答案 0 :(得分:2)
经过一些更多的研究(相当多的只是试验和错误,说实话)似乎RazorPage.ExecuteAsync()
不是正确的方法。使用正确键入的RazorView.RenderAsync()
来调用ViewDataDictionary<>
可以解决问题!
以下是最终代码。
public override void OnActionExecuted(ActionExecutedContext context) {
var pageFactory = (IRazorPageFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorPageFactory));
var viewEngine = (IRazorViewEngine)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewEngine));
var viewFactory = (IRazorViewFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewFactory));
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var page = pageFactory.CreateInstance($"~/Areas/Widgets/DefaultViews/Widgets/{widgetInfo.Type}.cshtml");
var view = (RazorView)viewFactory.GetView(viewEngine, page, true);
var vddType = typeof(ViewDataDictionary<>);
var vddGeneric = vddType.MakeGenericType(widgetInfo.Model.GetType());
dynamic viewDataDictionary = Activator.CreateInstance(vddGeneric, new EmptyModelMetadataProvider(), new ModelStateDictionary());
var actionContext = new ActionContext(context.HttpContext, context.RouteData, context.ActionDescriptor);
var viewContext = new ViewContext(actionContext, view, viewDataDictionary, sw);
viewContext.ViewData.Model = widgetInfo.Model;
view.RenderAsync(viewContext).GetAwaiter().GetResult();
sw.Flush();
var renderedWidget = sb.ToString();
}
我对dynamic
的{{1}}并不是特别满意,但它确实有效。可能还有其他方法可以做到这一点,这样做更容易,但现在已经足够好了。
在ViewDataDictionary
和ASP.Net MVC 6.0.0-beta3-12628
上进行了测试。
答案 1 :(得分:1)
你正在深入探索渲染管道,这非常简单。尝试使用小部件集合创建模型。在主视图中执行以下操作:
foreach(var widget in Model.Widgets) { Html.DisplayFor(widget) }
然后在/views/displaytemplates/widget.cshtml中创建一个视图,然后通过将其转换为更具体的小部件类型,然后使用它调用Html.DisplayFor,将小部件分解为正确的模板。
** PSEUDO代码**
@model Widget
if (Widget is Widget1Type) Html.DisplayFor((Widget1Type)Widget)
if (Widget is Widget2Type) Html.DisplayFor((Widget2Type)Widget)
if (Widget is Widget3Type) Html.DisplayFor((Widget3Type)Widget)
if (Widget is Widget4Type) Html.DisplayFor((Widget4Type)Widget)
然后为这些小部件创建视图 /视图/ displaytemplates / widget1type:
@model Widget1Type
...Stuff here...