Can't define a custom renderer lambda in MVC

2019-06-01 01:13发布

问题:

I want to define a custom renderer as a lambda in my MVC view that I can use it in a partial to render the same thing multiple times. I plan to store it in the view data. So far I have created this extension method to store the renderer:

public static class HtmlHelperExtensions
{
   public static void DefineRenderer<TModel>(this HtmlHelper<TModel> html, string rendererName, Action renderer)
   {
      html.ViewData["_Renderer" + rendererName] = renderer;
   }
}

I'm trying to define the renderer in my view, but it isn't working; I assume my syntax is off. Could somebody tell me what I'm doing wrong here? I just want it to render the test paragraph when called:

@Html.DefineRenderer("AnalysisTableHeader", () => {
    <p>test paragraph</p>
@});

回答1:

The DefineRenderer-method needs to return anything else than void, e.g. IHtmlString to call it with the razor @-syntax or else you will need to call it like this:

@{
    Html.DefineRenderer("AnalysisTableHeader", () => {
        <p>test paragraph</p>
    });
}

Edit: Sorry, I've seen that the renderer parameter is of type System.Action. I think it must be of type System.Func<dynamic, HelperResult> and you need to call it

@{ Html.DefineRenderer("AnalysisTableHeader", @<text><p>test paragraph</p></text>); }

for example. You can then later render it like this: render(null).ToHtmlString(). Anyway beware that you might get problems with partial view caching if you do stuff like this in views.



回答2:

I'm still not 100% sure if that's what you need but maybe it'll help. Define @helper in your partial view and based on value from Model decide which version to render:

Declare enum with header types in your code

public enum HeaderTypes
{
    AnalysisTable,
    SomethingElse
}

Then in your view

@helper RenderHeader(HeaderTypes headerType) {
    switch (headerType)
    {
        case HeaderTypes.AnalysisTable:
            @: <p>your html</p>
            break;
        default: 
            @: <p>default</p>
            break;
    }
}

@RenderHeader(HeaderTypes.None)
@RenderHeader(HeaderTypes.AnalysisTable)

Or you can just do switch based on string value or something else.



回答3:

With some inspiration from @mariozski's comment, I managed to get the behaviour I wanted to work. I use a @helper as the renderer. The model I pass to the partial contains the result of the helper render, ie. a HelperResult. So it looks like this:

public class AnalysisResponseTableViewModel {
    public HelperResult HeaderTypeRowRenderer { get; set; }
    public List<AnalysisUserResponseViewModel> Responses { get; set; }
}

Then, the calling view calls the partial like this:

@helper RenderHeaderTypeRow() {
    <tr class="headerTypeRow">
        <td>Header type row</td>
        <td>Goes here</td>
    </tr>
}

@Html.Partial("AnalysisResponseTableContentsPartial",
    new AnalysisResponseTableViewModel {
        Responses = Model.OverallCaseStudyUserResponses,
        HeaderTypeRowRenderer = RenderHeaderTypeRow()
    }
)

And finally the partial itself can render the 'header type row' multiple times like this:

@Html.Raw(Model.HeaderTypeRowRenderer.ToHtmlString())
@{bool reachedSummaryRows = false;}
@foreach (var response in Model.Responses) {
    if (!reachedSummaryRows && !response.IsPass.HasValue) {
        reachedSummaryRows = true;
        @:@Html.Raw(Model.HeaderTypeRowRenderer.ToHtmlString())
    }

    // other table rows here
}