Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EditorTemplate inheritance - is there a way

EditorTemplates are great since they allow some kind of "polymorphism" in razor views. But I am missing one "brick" to complete the polymorphism support:

Can an EditorTemplate for a special type inherit from the EditorTemplate for the general type?


Long version:

Given

class SpecialChild : GeneralChild { }

class Parent
{
    GeneralChild AGeneralChild { get; set; }
    SpecialChild ASpecialChild { get; set; }
}

and two editor templates

@* GeneralChild.cshtml *@

@model GeneralChild
<span>GeneralChild</span>


@* SpecialChild.cshtml *@

@model SpecialChild
<span>SpecialChild is a</span> <span>GeneralChild</span>

What I get (which is why I call it "polymorphism") is:

@* Index.cshtml *@

@Html.EditorFor(m => m.AGeneralChild)
// prints "<span>GeneralChild</span>", however

@Html.EditorFor(m => m.ASpecialChild)
// prints "<span>SpecialChild is a</span> <span>GeneralChild</span>"

That is, even though SpecialChild is a GeneralChild and there is a template for GeneralChild, it auto-selects the SpecialChild.cshtml template. Furthermore, if I remove that template, it falls back to the GeneralChild.cshtml template. In other words, it is possible to reuse a general template or to override it if necessary.

Now for what I would really like:

I would like to reuse the GeneralChild.cshtml template to define the SpecialChild.cshtml template, like a "base method" call in C#. In pseudo-code:

@* SpecialChild.cshtml *@

baseEditorFor()

@section SpecificPart
{
    <span>SpecialChild is a </span>
}


@* GeneralChild.cshtml *@

@Html.RenderSection("SpecificPart") <span>GeneralChild</span>

Is something like that supported?


What I have tried so far:

GeneralChild.cshtml:

@model GeneralChild
@{
    var extend = ViewData.ContainsKey("Extend")
        ? (MvcHtmlString)ViewData["Extend"]
        : null;
}

@if (extend != null) { @extend }
<span>GeneralChild</span>

SpecificChild.cshtml:

@model SpecialChild
@Html.EditorFor(
    m => m,         // call editor for my model
    "GeneralChild", // but call "base" instead of "this"
    new
    {
        // Hand the HTML to insert as ViewData parameter
        Extend = new MvcHtmlString("<span>SpecialChild is a </span>")
    })

Unfortunately, @Html.EditorFor(m => m) does not do anything. That makes sense because m => m is not the same expression as the original m => m.ASpecialChild.

I thought I could build up the expression tree by hand, but then I realized that the type arguments within the editor template are (of course) different from the ones in the Index.cshtml. @Html in the original call is typed <Parent> whereas within the template it is <SpecialChild>.


Then I tried another approach which is the closest I got so far:

Within the Index.cshtml I define a razor helper:

@helper SpecialChildEditorFor(Expression<Func<Parent,SpecialChild>> expression)
{
    @Html.EditorFor(
        expression,
        "GeneralChild",
        new { Extend = new MvcHtmlString("<span>SpecialChild is a </span>") })
}

Then I call this instead of EditorFor:

@SpecialChildEditorFor(m => m.ASpecialChild)

But of course this lacks the entirety of the the initially mentioned advantages - I can't simply drop this snippet in the EditorTemplates directory, thus "overriding" the GeneralChild.cshtml template. Also, it needs to be explicitly called (so we lost "polymorphism", too). Even more, the razor helper is tied to the Index.cshtml page: * It has to be defined within the page where it is used. * It relies on expression to have the same type arguments as the one the page needs.

like image 461
chiccodoro Avatar asked Oct 31 '14 10:10

chiccodoro


1 Answers

Use Partial in editor template insted of @Html.EditorFor(m => m, ...):

@model SpecialChild
@{
    ViewData["Extend"] = new MvcHtmlString("<span>SpecialChild is a </span>");
}
@Html.Partial("~/Views/Shared/EditorTemplates/GeneralChild.cshtml", Model)
like image 125
py3r3str Avatar answered Nov 13 '22 00:11

py3r3str