在ASP.Net 5中渲染RazorPage时无法设置模型

时间:2015-04-10 15:29:13

标签: c# razor asp.net-core-mvc

我正在使用新的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-12628KRE 1.0.0-beta3-11014,但很可能会尽快升级到beta4。

任何人都知道为什么在Model上设置ViewData是不够的,或者我需要做些什么才能使其正常工作?

2 个答案:

答案 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}}并不是特别满意,但它确实有效。可能还有其他方法可以做到这一点,这样做更容易,但现在已经足够好了。

ViewDataDictionaryASP.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...