Inherited interface property not found by Model Bi

2020-07-05 07:16发布

问题:

UPDATE: Problem Solved.

Darin Dimitrov's answer contains a link to another, related question. One of the proposed solutions on that page ended up working for us.


Original Question

The code sample below works in MVC 2 but throws an exception in MVC 3. Does anyone know why MVC 3 introduced this breaking change? Is there a way to get this working in MVC 3 while still allowing me to describe the viewmodel as an Interface from within the view?

There is another related question on StackOverflow, but it doesn't give me any info as to why there is a difference between MVC 2 and MVC 3.

Model

public interface IPerson {
    string Name { get; set; }
}

public interface ISpy : IPerson {
    string CodeName { get; set; }
}

public class Spy : ISpy {
    public string Name { get; set; }
    public string CodeName { get; set; }
}

Controller Action

public ActionResult Index() {
    var model = new Spy { Name = "James Bond", CodeName = "007" };
    return View(model);
}

MVC2 View (Works perfectly)

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MvcApp.Models.ISpy>" %>
<p>CodeName: <%: Html.TextBoxFor(x => x.CodeName) %></p>
<p>Name: <%: Html.TextBoxFor(x => x.Name) %></p>

MVC3 View (Throws exception)

@model MvcApp.Models.ISpy
<p>Name: @Html.TextBoxFor(x => x.Name)</p>
<p>CodeName: @Html.TextBoxFor(x => x.CodeName)</p>

MVC3 Exception Information

I am showing the relevant exception info below, but you can view the entire output of the error page here: https://gist.github.com/1443750.

Runtime Exception
System.ArgumentException: The property **`MvcApp.Models.ISpy.Name`** could not be found.

Source Error:

Line 1:  @model MvcApp.Models.ISpy
Line 2:  <p>CodeName: @Html.TextBoxFor(x => x.CodeName)</p>
Line 3:  <p>Name: @Html.TextBoxFor(x => x.Name)</p>


Stack Trace:

[ArgumentException: The property MvcApp.Models.ISpy.Name could not be found.]
   System.Web.Mvc.AssociatedMetadataProvider.GetMetadataForProperty(Func`1 modelAccessor, Type containerType, String propertyName) +502169
   System.Web.Mvc.ModelMetadata.GetMetadataFromProvider(Func`1 modelAccessor, Type modelType, String propertyName, Type containerType) +101
   System.Web.Mvc.ModelMetadata.FromLambdaExpression(Expression`1 expression, ViewDataDictionary`1 viewData) +421
   System.Web.Mvc.Html.InputExtensions.TextBoxFor(HtmlHelper`1 htmlHelper, Expression`1 expression, IDictionary`2 htmlAttributes) +58
   System.Web.Mvc.Html.InputExtensions.TextBoxFor(HtmlHelper`1 htmlHelper, Expression`1 expression) +50
   ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Projects\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml:3

Help us Darin Dimitrov, you're our only hope.

回答1:

I have already brought this issue to the attention of Microsoft. Unfortunately the response I got was: sorry that's by design. Personally the conclusion I draw from such an answer is that the MVC team is designing a buggy product if that's by design. And they are not to be blamed for taking such a design decision, they are for blaming for not mentioning it anywhere in the breaking changes section of the release notes document. Had they mentioned it, then it would have been our fault when we took the decision for migrating even if we deliberately knew about the breaking change.

Anyway, here's a related question.

And here's an answer from the MVC team:

Unfortunately, the code was actually exploiting a bug that was fixed, where the container of an expression for ModelMetadata purposes was inadvertently set to the declaring type instead of the containing type. This bug had to be fixed because of the needs of virtual properties and validation/model metadata.

Having interface-based models is not something we encourage (nor, given the limitations imposed by the bug fix, can realistically support). Switching to abstract base classes would fix the issue.



回答2:

Change this line:

@model MvcApp.Models.ISpy

To

@model MvcApp.Models.Spy


回答3:

I found a pathetic workaround which allows you to use this model structure:

@model MvcApp.Models.IPerson
<p>Name: @Html.TextBoxFor(x => x.Name)</p>
@Html.Partial('_SpyBox')

And in _SpyBox.cshtml

@model MvcApp.Models.ISpy
<p>CodeName: @Html.TextBoxFor(x => x.CodeName)</p>

Meh.