将 Spark 视图渲染为字符串

发布于 2024-10-05 05:01:53 字数 1736 浏览 0 评论 0 原文

我正在尝试将部分视图呈现为字符串,以便它可以作为 HTML 返回到 jquery ajax 调用。经过大量搜索,我找到了这段代码。

public string RenderAsString(string viewName, string modelName, object model)
{
    // Set up your spark engine goodness.
    var settings = new SparkSettings().SetPageBaseType(typeof(SparkView));
    var templates = new FileSystemViewFolder(Server.MapPath("~/Views"));
    var engine = new SparkViewEngine(settings) { ViewFolder = templates };


    // "Describe" the view (the template, it is a template after all), and its details.
    var descriptor = new SparkViewDescriptor().AddTemplate(@"Shared\" + viewName + ".spark");

    // Create a spark view engine instance
    var view = (SparkView)engine.CreateInstance(descriptor);

    // Add the model to the view data for the view to use.
    view.ViewData[modelName] = model;

    // Render the view to a text writer.
    var writer = new StringWriter(); view.RenderView(writer);

    // Convert to string
    return writer.ToString();
}

但是当执行以下行时:

var view = (SparkView)engine.CreateInstance(descriptor);

我收到以下错误:

动态视图编译失败。一个 需要对象引用 非静态字段、方法或属性 'DomainModel.Entities.Region.Id.get。

这是我的部分观点:

<ViewData Model="Region" />

<div id="${ Region.Id }" class="active-translation-region-widget" >
    <label>${Region.RegionName}</label>
        ${ Html.CheckBox("Active") }
</div> 

它似乎无法识别该模型。

PS 当我像这样从父视图调用视图时,

<for each="var region in Model">
    <ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
</for>

它会完美呈现。我做错了什么?

I'm trying to render a partial view as a string so it can be returned as HTML to a jquery ajax call. After a lot of searching I found this code.

public string RenderAsString(string viewName, string modelName, object model)
{
    // Set up your spark engine goodness.
    var settings = new SparkSettings().SetPageBaseType(typeof(SparkView));
    var templates = new FileSystemViewFolder(Server.MapPath("~/Views"));
    var engine = new SparkViewEngine(settings) { ViewFolder = templates };


    // "Describe" the view (the template, it is a template after all), and its details.
    var descriptor = new SparkViewDescriptor().AddTemplate(@"Shared\" + viewName + ".spark");

    // Create a spark view engine instance
    var view = (SparkView)engine.CreateInstance(descriptor);

    // Add the model to the view data for the view to use.
    view.ViewData[modelName] = model;

    // Render the view to a text writer.
    var writer = new StringWriter(); view.RenderView(writer);

    // Convert to string
    return writer.ToString();
}

But when the following line executes:

var view = (SparkView)engine.CreateInstance(descriptor);

I get the following error:

Dynamic view compilation failed. An
object reference is required for the
non-static field, method, or property
'DomainModel.Entities.Region.Id.get.

This is my partial view:

<ViewData Model="Region" />

<div id="${ Region.Id }" class="active-translation-region-widget" >
    <label>${Region.RegionName}</label>
        ${ Html.CheckBox("Active") }
</div> 

It doesn't seem to recognise the model.

P.S. When I call the view from a parent view like so

<for each="var region in Model">
    <ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
</for>

It renders perfectly. What am I doing wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

甜尕妞 2024-10-12 05:01:53

仅从外观上看,我认为以下行是问题所在:

<ViewData Model="Region" />

相反,它应该显示为:

<viewata model="Region" />

注意小写的“模型”。这是因为 model 是一种特殊情况,因为它在幕后执行到强类型视图模型的转换。最上面的一个将在生成的视图类中定义一个名为 Model 的变量,并将值 Region 分配给它。使用下面的小写选项实际上会创建一个 Model 变量,但也会将其转换为来自 ViewData 字典的 Region 强类型实例。

注意 当在代码中使用 Model 时,就像您在 foreach 循环中所做的那样,它需要是大写,这在您的代码中是正确的案件。再次强调,这是唯一的特殊情况,因为它从 ViewData 字典中提取强类型模型。

另一件事 - 必须在父视图中声明,并且每个页面只能定义一次,因此您不能在分部视图中重新定义它。如果它是部分视图,您应该通过传递模型的一部分来使用它,就像上面第二个示例中所做的那样。

上述异常的原因是因为它试图将 Id 属性作为 Region Type 的静态项获取,而不是查询 <作为视图模型一部分的 Region 实例 上的 code>Id 属性。

附带说明一下,到达您想要的位置的代码有点混乱。您可以通过查看一些 直接使用示例,但我知道这可能只是看到它工作的一个峰值...:)

更新以响应您的跟进问题/答案

我相当确定问题出在传递 Region 上 进入以下调用:

<ActiveTranslationRegion Region="region" if="region.Active==true">

... 再次归结为命名。是的,正如我之前所说,每个视图只能有一个模型,因此您需要做的是从部分顶部删除以下内容:

<viewdata model="Region" />

这就是导致问题的原因。然后,我将像这样重命名进入您的部分的项目:

<ActiveTranslationRegion ActiveRegion="region" if="region.Active==true">

然后您的部分将如下所示:

<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
    <input type="hidden" name="Id" value="${ActiveRegion.Id}" />        
    <label class="region-name">${ ActiveRegion.RegionName }</label>
    <input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>

注意我使用ActiveRegion因为在Spark解析器中,ActiveRegion 被声明为变量,并在循环 for 循环 时分配当前作用域中 region 的值。无需严格遵守模型 - 因为您已经将模型的一部分传递给了您已声明ActiveRegion。哦,如果您确实愿意,您可以坚持使用名称 Region,我只是更改它来表明观点,因为您有一个名为 Type Region 在你的代码中,我不太喜欢使用与类型相同的变量名称可能带来的奇怪问题。另外,它使它更清晰一些。

调用 Html.RenderPartial 方法的缺点并不是立即显而易见的。您失去的一件事是 Spark 提供的 3 遍渲染。如果您使用标签语法(这是更好的),您将能够将部分中的部分堆叠到多个级别,向下传递变量,为每个部分提供它们需要的堆栈。它变得真正强大 - 开始思考数据网格类型结构,其中行和单元格是单独的部分,它们从模型中获取所需的变量,所有这些都在单独的可管理视图文件中保持良好和干净。但不要就此止步,开始考虑基于变量或三列布局来定位页眉和页脚内容,这些布局创建一个仪表板,在多个深度的单独堆叠的部分上呈现各种类型。

当您使用沼泽标准 ASP.NET MVC Helper 方法 Html.RenderPartial() 时,您会失去所有灵活性 - 小心这样做,很可能有像上面这样的解决方案。

让我知道这是否有效...
一切顺利
罗布·G

Just from looking at it, I think the following line is the problem:

<ViewData Model="Region" />

Instead it should read:

<viewata model="Region" />

Note the lower case "model". This is because model is a special case since behind the scenes it performs a cast to a strongly typed viewmodel. The top one will define a variable called Model in the generated view class and assign the value Region to it. Using the lowercase option below will actually create a Model variable, but also cast it to strongly typed instance of Region that comes from the ViewData dictionary.

Note When using Model in the code though, like you did in the for each loop, it needs to be upper case which is correct in your case. Once again, this is the only special case because it's pulling a strongly typed model from the ViewData dictionary.

One other thing - <viewata model="Region" /> must be declared in the parent view, and it can only be defined once per page, so you cannot redefine it in a partial view. If it's a partial view, you should rather use it by passing in a part of the model like you have done in your second example above.

The reason for your exception above is because it's trying to get the Id property as a static item off the Region Type, rather than querying the Id property on your instance of Region as part of your viewmodel.

As a side note, the code to get where you want is a little mangled. You can find neater ways of doing what you want by checking out some of the Direct Usage Samples, but I understand this was probably just a spike to see it working... :)

Update in response to your follow up question/answer

I'm fairly sure that the problem is with passing the Region into the following call:

<ActiveTranslationRegion Region="region" if="region.Active==true">

... is again down to naming. Yes, you can only have one model per view as I said before, so what you need to do is remove the following from the top of your partial:

<viewdata model="Region" />

That's what's causing an issue. I would then rename the item going into your partial like so:

<ActiveTranslationRegion ActiveRegion="region" if="region.Active==true">

and then your partial would look like this:

<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
    <input type="hidden" name="Id" value="${ActiveRegion.Id}" />        
    <label class="region-name">${ ActiveRegion.RegionName }</label>
    <input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>

Note I'm using ActiveRegion because in the Spark parser, ActiveRegion gets declared as a variable and assigned the value of region in the current scope as you loop through the for loop. No need to stick religiously to the model - because you've gone and passed in a piece of the model now that you've declared as ActiveRegion. Oh, and you could stick with the name Region if you really want, I just changed it to make a point, and because you've got a Type called Region in your code and I'm not a big fan of the quirky issues using the same name for a variable as a type can bring about. Plus it makes it a little clearer.

The disadvantage of calling the Html.RenderPartial method is not immediately obvious. One thing you lose is the 3-pass rendering that Spark provides. If you use the tag syntax (which is preferable) you'll be able to stack partials within partials to multiple levels down passing variables that feed each partial what they need down the stack. It gets really powerful - start thinking data grid type structures where rows and cells are individual partials that are fed the variables they need from the model, all kept nice and clean in separate manageable view files. Don't stop there though, start thinking about targeting header and footer content base on variables or three column layouts that create a dashboard that renders all sorts on individually stacked partials many levels deep.

You lose all of that flexibility when you use the bog standard ASP.NET MVC Helper method Html.RenderPartial() - careful of doing that, there's more than likely a solution like the one above.

Let me know if that works...
All the best
Rob G

韶华倾负 2024-10-12 05:01:53

我对代码和视图进行了相当多的重构。最后,我真正想要实现的是有一个父视图(未显示)迭代 IEnumerable 并为每次迭代渲染一个部分视图(ActiveTranslationRegion),它渲染一些 Html 来表示区域模型。

我还希望能够通过 ajax 调用来调用操作方法,以通过向其传递单独的区域模型来呈现单独的 ActiveTranslationRegion 部分视图。我相应地重构了代码。

部分视图 (_ActiveTranslationRegion.spark)

<viewdata model="Region" />

<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
    <input type="hidden" name="Id" value="${Model.Id}" />        
    <label class="region-name">${ Model.RegionName }</label>
    <input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>

请注意,通过使用我可以按照 RobertTheGrey 建议的方式引用视图中的模型(见上文)。

我删除了所有代码以将视图作为字符串返回,并简单地创建了一个返回partialViewResult的操作方法方法:

[UnitOfWork]
public PartialViewResult ActivateRegion(int id)
{
    var region = _repos.Get(id);
    if (region != null)
    {    
        region.Active = true;
        _repos.SaveOrUpdate(region);
    }

    return PartialView("_ActiveTranslationRegion", region);
}

我必须做的一件事是修改我的父视图,使其看起来像这样:

<div id="inactive-translation-regions-panel">
    <h3 class="ui-widget-header">Inactive Regions</h3>
    <for each="var region in Model">
        <span if="region.Active==false">
                # Html.RenderPartial("_InActiveTranslationRegion", region);
        </span>
    </for>
</div>

以前我有以下内容:

<div id="inactive-translation-regions-panel">
   <for each="var region in Model">
          <ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
   </for>
</div>

注意我有调用 Html.RenderPartial 而不是使用该元素。如果我尝试使用该元素(我更愿意这样做),我会收到以下错误:

Only one viewdata model can be declared. IEnumerable<Region> != Region

有解决该问题的方法吗?

更新:

我尝试了你的建议,但没有成功。回顾一下这个问题,我想在两种不同的情况下使用部分。在第一个实例中,我有一个使用 IEnumerable 模型的父视图,部分仅使用 Region 作为其模型。因此,在父级中,我迭代 IEnumerable 并将 Region 传递给部分。在第二个实例中,我想从操作方法调用 PartialView("_ActiveTranslationRegion", region) 。如果我从部分中删除 ,我会收到有关模型的错误消息。解决我发现的问题的最佳方法是将绑定添加到 bindings.xml 文件:

<element name="partial"># Html.RenderPartial("@name", @model);</element>

(注意:将绑定文件中的此条目全部保留在同一行似乎非常重要)

我仍然可以如上所述从操作方法中调用部分,并将其传递为区域作为模型,但我也可以用更像“html”的标签替换父视图中对 Html.RenderPartial 的调用:

<partial name="_ActiveTranslationRegion" model="region" />

所以我的父级现在的视图看起来更像是这样的:

<div id="inactive-translation-regions-panel">
    <h3 class="ui-widget-header">Inactive Regions</h3>
    <for each="var region in Model">
        <span if="region.Active==false">
               <partial name="_ActiveTranslationRegion" model="region" />
        </span>
    </for>
</div>

当然,在幕后它仍在调用

# Html.RenderPartial("_ActiveTranslationRegion", region);

但它是我们能想到的最佳解决方案。

问候,
西蒙

I refactored the code and views quite a bit. In the end all I'm really trying to acheive is have a parent view (not shown) iterate over an IEnumerable and for each iteration render a partial view (ActiveTranslationRegion) which renders some Html to represent a region model.

I also want to be a able to call an action method via an ajax call to render an indivual ActiveTranslationRegion partial view by passing it an individual region model. I've refactored the code accordingly.

Partial view (_ActiveTranslationRegion.spark)

<viewdata model="Region" />

<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
    <input type="hidden" name="Id" value="${Model.Id}" />        
    <label class="region-name">${ Model.RegionName }</label>
    <input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>

Notice by using I can refer to Model within the view as RobertTheGrey suggested (see above) .

I removed all the code to return the view as a string and simply created an action method method that returned a partialViewResult:

[UnitOfWork]
public PartialViewResult ActivateRegion(int id)
{
    var region = _repos.Get(id);
    if (region != null)
    {    
        region.Active = true;
        _repos.SaveOrUpdate(region);
    }

    return PartialView("_ActiveTranslationRegion", region);
}

One thing I had to do was amend my parent view to look like so:

<div id="inactive-translation-regions-panel">
    <h3 class="ui-widget-header">Inactive Regions</h3>
    <for each="var region in Model">
        <span if="region.Active==false">
                # Html.RenderPartial("_InActiveTranslationRegion", region);
        </span>
    </for>
</div>

Where previously I had the following:

<div id="inactive-translation-regions-panel">
   <for each="var region in Model">
          <ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
   </for>
</div>

Notice I have to call the Html.RenderPartial rather than use the element. If I try and use the element (which I would prefer to do) I get the following error:

Only one viewdata model can be declared. IEnumerable<Region> != Region

Is there a way round that problem?

Update:

I tried your recommendation but with no luck. To recap the problem, I want to use the partial in 2 different situations. In the first instance I have a parent view that uses a model of IEnumerable<Region>, the partial simply uses Region as its model. So in the parent I iterate over the IEnumerable and pass Region to the partial. In the second instance I want to call PartialView("_ActiveTranslationRegion", region) from an action method. If I remove the <viewdata model="Region" /> from the partial I get an error complaining about the model. The best way round the problem I have found is to add a binding to the bindings.xml file:

<element name="partial"># Html.RenderPartial("@name", @model);</element>

(Note: It seems very important to keep this entry in the bindings file all on te same line)

This way I can still call the partial from the action method as described above and pass it a Region as the model, but I can also replace my call to Html.RenderPartial in the parent view with a more 'html' like tag:

<partial name="_ActiveTranslationRegion" model="region" />

So my parent view now looks more like this:

<div id="inactive-translation-regions-panel">
    <h3 class="ui-widget-header">Inactive Regions</h3>
    <for each="var region in Model">
        <span if="region.Active==false">
               <partial name="_ActiveTranslationRegion" model="region" />
        </span>
    </for>
</div>

Of course under the hood its still making a call to

# Html.RenderPartial("_ActiveTranslationRegion", region);

But its the best solution we could come up with.

Regards,
Simon

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文