EditorFor isn't working on derived type

2019-04-07 14:06发布

问题:

At least, I think that's related to the problem. My scenario is this:

I've got a number of business entities with common fields, and each one has custom fields unique to that entity. So in code, this is modeled as an EntityBase class, and there are a number of classes derived from this, e.g., Derived.

To make a reusable UI, I've got a view called EntityBase.vbhtml that looks like this:

@ModelType EntityBase

@Using Html.BeginForm("Edit", Model.GetType.Name)
    @* show the editor template for the derived type *@
    @* !!the next line renders nothing!! *@
    @Html.EditorFor(Function(x) Model, Model.GetType.Name)

    [show a bunch of stuff common to all EntityBase objects]
End Using

and then one called Derived.vbhtml for the derived classes that does this:

@ModelType Derived
[show an EditorFor for various Derived-specific fields]

Then, when you navigate to \Derived\Edit\123, it returns the default view Derived\Edit.vbhtml, which just does this:

@Html.EditorForModel("EntityBase")

In this way, controllers just return the expected default Edit view, which is a one-liner call to the EntityBase view, which does its thing and invokes the Derived to render the derived class stuff that it has no knowledge of.

I thought this was unremarkable, but it doesn't work. As marked in the view code, when I call EditorForModel within the base class view, specifying the name of the derived class for use as a template, it doesn't render anything. I've tested that if I call this exact same line of code in the top level Edit template, it works fine. So there's something about the inheritance that MVC doesn't like, but I can't see what. Please help!

Update: It works as I would expect if instead of EditorFor I use Partial (and move the corresponding template to the Shared folder from the EditorTemplates folder), but that's not a great solution because I think it's confusing not to follow the naming convention for EditorFor templates.

回答1:

It appears that, while MVC won't locate the named template in this circumstance, it will find it if you specify the full path to the template. So, rather than fight this any further, I implemented the following helper function:

<Extension()> _
Public Function EditorForObject(Of T, TValue)(ByVal htmlHelper As HtmlHelper(Of T), ByVal obj As TValue) As IHtmlString
    Dim sTemplateName = "~/Views/Shared/EditorTemplates/" & obj.GetType.Name & ".vbhtml"

    'Return htmlHelper.EditorFor(Function(x) obj) <-- this should work but doesn't
    Return htmlHelper.Partial(sTemplateName, obj)
End Function

In English, this means: ask the object for its type name, form the explicit path to the editor template for that type, and then invoke HtmlHelper.Partial, specifying the object and the full path to the template. I'm sure this could be more general (and not hardcoded for vb), but it works.

Then the usage is like this:

@Html.EditorForObject(Model)

and actually, this is even better than what I was trying to do, which is much messier:

@Html.EditorFor(Function(x) Model, Model.GetType.Name)

Even without the template lookup problem, this would be handy, because it's convenient to be able to pass an object for editing (or display), rather than a dummy lambda that just returns that object.

Still, I think the lookup problem must be a bug in MVC. (If I ever get time, I guess I can check the source code.) Can anyone confirm or comment on this?