mvc5 - ASP.NET MVC 3 HtmlHelper Exception no reconoce ModelMetadata en la interfaz heredada
www asp net mvc mvc4 (6)
Además de la respuesta de Anthony Johnston , es posible que obtenga excepciones al usar DataAnnotations, ya que el método AssociatedValidatorProvider.GetValidatorsForProperty () intentará usar la interfaz hereditaria como tipo de contenedor en lugar de la base y, por lo tanto, no podrá encontrar la propiedad nuevamente.
Este es el código reflejado del método GetValidatorsForProperty (es la segunda línea la que hace que la variable propertyDescriptor sea nula y, por lo tanto, se lance la excepción):
private IEnumerable<ModelValidator> GetValidatorsForProperty(ModelMetadata metadata, ControllerContext context)
{
ICustomTypeDescriptor typeDescriptor = this.GetTypeDescriptor(metadata.ContainerType);
PropertyDescriptor propertyDescriptor = typeDescriptor.GetProperties().Find(metadata.PropertyName, true);
if (propertyDescriptor != null)
{
return this.GetValidators(metadata, context, propertyDescriptor.Attributes.OfType<Attribute>());
}
else
{
object[] fullName = new object[] { metadata.ContainerType.FullName, metadata.PropertyName };
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyNotFound, fullName), "metadata");
}
}
Si es así, creo que el siguiente código puede ayudar, ya que se asegura de que el ContainerType esté configurado al tipo en el que se encuentra la propiedad, no al tipo del modelo de vista.
Descargo de responsabilidad: parece funcionar bien, pero aún no lo he probado completamente, ¡por lo que podría tener efectos no deseados! También entiendo que no está escrito a la perfección, pero estaba tratando de mantener el formato similar a la respuesta anterior para facilitar la comparación. :)
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
public override ModelMetadata GetMetadataForProperty(
Func<object> modelAccessor, Type containerType, string propertyName)
{
if (containerType == null)
{
throw new ArgumentNullException("containerType");
}
if (String.IsNullOrEmpty(propertyName))
{
throw new ArgumentException(
"The property '{0}' cannot be null or empty", "propertyName");
}
var containerTypeToUse = containerType;
var property = GetTypeDescriptor(containerType)
.GetProperties().Find(propertyName, true);
if (property == null
&& containerType.IsInterface)
{
var foundProperty = (from t in containerType.GetInterfaces()
let p = GetTypeDescriptor(t).GetProperties()
.Find(propertyName, true)
where p != null
select (new Tuple<System.ComponentModel.PropertyDescriptor, Type>(p, t))
).FirstOrDefault();
if (foundProperty != null)
{
property = foundProperty.Item1;
containerTypeToUse = foundProperty.Item2;
}
}
if (property == null)
{
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
"The property {0}.{1} could not be found",
containerType.FullName, propertyName));
}
return GetMetadataForProperty(modelAccessor, containerTypeToUse, property);
}
}
Después de actualizar a MVC 3 RTM, recibí una excepción en la que funcionaba anteriormente.
Aquí está el escenario. Tengo varios objetos que utilizan las mismas interfaces subyacentes IActivity y IOwned.
IActivity implements IOwned (another interface)
public interface IActivity:IOwned {...}
public interface IOwned
{
int? AuthorId {get;set;}
}
Tengo una vista parcial que usa IActivity para reutilizarla de otros parciales concretos.
Aquí está la definición de la Actividad Parcial.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => item.AuthorId) %>
Sin embargo, lanza una excepción. No se puede encontrar AuthorId en el ModelMetadata.
Supongo que en la versión anterior se observaron las interfaces implementadas por IActivity.
¿Alguna idea, sugerencia, sin duplicar interfaces similares en todas partes?
Copia la traza de pila abajo.
[ArgumentException: The property IActivity.AuthorId could not be found.]
System.Web.Mvc.AssociatedMetadataProvider.GetMetadataForProperty(Func`1 modelAccessor, Type containerType, String propertyName) +498313
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) +393
System.Web.Mvc.Html.InputExtensions.HiddenFor(HtmlHelper`1 htmlHelper, Expression`1 expression, IDictionary`2 htmlAttributes) +57
System.Web.Mvc.Html.InputExtensions.HiddenFor(HtmlHelper`1 htmlHelper, Expression`1 expression) +51
ASP.views_shared_activity_ascx.__Render__control1(HtmlTextWriter __w, Control parameterContainer) in c:/Users/.../Documents/Visual Studio 2010/Projects/ngen/trunk/.../Views/Shared/Activity.ascx:3
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +109
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Control.Render(HtmlTextWriter writer) +10
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +208
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Page.Render(HtmlTextWriter writer) +29
System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer) +43
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3060
Del equipo de MVC:
Desafortunadamente, el código en realidad estaba explotando un error que se solucionó, donde el contenedor de una expresión para fines de ModelMetadata se configuró inadvertidamente al tipo de declaración en lugar del tipo que contiene. Este error debía solucionarse debido a las necesidades de las propiedades virtuales y los metadatos de validación / modelo.
Tener modelos basados en la interfaz no es algo que fomentemos (ni, dadas las limitaciones impuestas por la corrección de errores, podemos admitir de manera realista). Cambiar a clases base abstractas arreglaría el problema.
Gracias a Burcephal, cuya respuesta me indicó la dirección correcta.
Puede crear un MetaDataProvider que solucionará este problema, el código aquí se agrega al código en la clase base que verifica la propiedad en las interfaces implementadas de un modelo que es en sí mismo una interfaz.
public class MyMetadataProvider
: EmptyModelMetadataProvider {
public override ModelMetadata GetMetadataForProperty(
Func<object> modelAccessor, Type containerType, string propertyName) {
if (containerType == null) {
throw new ArgumentNullException("containerType");
}
if (String.IsNullOrEmpty(propertyName)) {
throw new ArgumentException(
"The property '{0}' cannot be null or empty", "propertyName");
}
var property = GetTypeDescriptor(containerType)
.GetProperties().Find(propertyName, true);
if (property == null
&& containerType.IsInterface) {
property = (from t in containerType.GetInterfaces()
let p = GetTypeDescriptor(t).GetProperties()
.Find(propertyName, true)
where p != null
select p
).FirstOrDefault();
}
if (property == null) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
"The property {0}.{1} could not be found",
containerType.FullName, propertyName));
}
return GetMetadataForProperty(modelAccessor, containerType, property);
}
}
y como se indicó anteriormente, configure su proveedor global.asax Application_Start
ModelMetadataProviders.Current = new MyMetaDataProvider();
Ha habido un cambio / error de última hora en ASP.NET MVC 3 en System.Web.Mvc.ModelMetadata. FromLambdaExpression
System.Web.Mvc.ModelMetadata. FromLambdaExpression
método System.Web.Mvc.ModelMetadata. FromLambdaExpression
que explica la excepción que está obteniendo:
ASP.NET MVC 2.0:
...
case ExpressionType.MemberAccess:
{
MemberExpression body = (MemberExpression) expression.Body;
propertyName = (body.Member is PropertyInfo) ? body.Member.Name : null;
containerType = body.Member.DeclaringType;
flag = true;
break;
}
...
ASP.NET MVC 3.0
...
case ExpressionType.MemberAccess:
{
MemberExpression body = (MemberExpression) expression.Body;
propertyName = (body.Member is PropertyInfo) ? body.Member.Name : null;
containerType = body.Expression.Type;
flag = true;
break;
}
...
Observe cómo a la variable containerType
se le asigna un valor diferente. Por lo tanto, en su caso en ASP.NET MVC 2.0 se le asignó el valor de IOwned
que es el tipo de declaración correcto de la propiedad AuthorId
, mientras que en ASP.NET MVC 3.0 se asigna a IActivity
y, posteriormente, cuando el marco intenta encontrar la propiedad choques
Esa es la causa. En lo que respecta a la resolución, esperaría alguna declaración oficial de Microsoft. No puedo encontrar ninguna información relevante sobre esto en el documento de Notas de la versión. ¿Es un error o alguna característica que necesita ser solucionada aquí?
Por ahora, puede usar el ayudante Html.Hidden("AuthorId")
no tipificado con fuerza o especificar IOwned
como tipo para su control (sé que ambos apestan).
Si estás interesado, he implementado un poco de trabajo en mi aplicación. Mientras revisaba el código fuente de MVC, descubrí que el método FromLambdaExpression que se menciona a continuación llamará al MetaDataProvider, que es un singleton reemplazable. Entonces podríamos simplemente implementar esa clase que realmente probará las interfaces internas heredadas si la primera no funciona. También subirá el árbol de las interfaces.
public class MyMetaDataProvider : EmptyModelMetadataProvider
{
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
try
{
return base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
}
catch(Exception ex)
{
//Try to go up to type tree
var types = containerType.GetInterfaces();
foreach (var container in types)
{
if (container.GetProperty(propertyName) != null)
{
try
{
return GetMetadataForProperty(modelAccessor, container, propertyName);
}
catch
{
//This interface did not work
}
}
}
//If nothing works, then throw the exception
throw ex;
}
}
}
y luego, solo puede forjar la implementación del MetaDataProvider en global.asax Application_Start ()
ModelMetadataProviders.Current = new MyMetaDataProvider();
No es el mejor código nunca, pero hace el trabajo.
Trate de usar encasillada. Funciona para mi proyecto aunque resharper lo destaca como redundante.
Para su código la solución sería
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => ((IOwned)item).AuthorId) %>