2009-09-09 15 views
6

Estoy intentando que UpdateModel llene un modelo que está configurado como solo una interfaz en tiempo de compilación. Por ejemplo, tengo:ASP.NET MVC UpdateModel con la interfaz

// View Model 
public class AccountViewModel { 
    public string Email { get; set; } 
    public IProfile Profile { get; set; } 
} 

// Interface 
public interface IProfile { 
    // Empty 
} 

// Actual profile instance used 
public class StandardProfile : IProfile { 
    public string FavoriteFood { get; set; } 
    public string FavoriteMusic { get; set; } 
} 

// Controller action 
public ActionResult AddAccount(AccountViewModel viewModel) { 
    // viewModel is populated already 
    UpdateModel(viewModel.Profile, "Profile"); // This isn't working. 
} 

// Form 
<form ... > 
    <input name='Email' /> 
    <input name='Profile.FavoriteFood' /> 
    <input name='Profile.FavoriteMusic' /> 
    <button type='submit'></button> 
</form> 

También tenga en cuenta que tengo un modelo de carpeta de encargo que hereda de DefaultModelBinder siendo utilizado que puebla iProfile con una instancia de StandardProfile en el método CreateModel overriden.

El problema es que FavoriteFood y FavoriteMusic nunca se completan. ¿Algunas ideas? Idealmente, todo esto se haría en la carpeta de modelo, pero no estoy seguro de que sea posible sin escribir una implementación completamente personalizada.

Gracias, Brian

Respuesta

2

tendría que comprobar el código de ASP.NET MVC (DefaultModelBinder), pero supongo que su reflexión sobre el tipo iProfile, y no el ejemplo, StandardProfile.

Así que busca cualquier miembro de IProfile que intente enlazar, pero es una interfaz vacía, por lo que se considera hecho.

Usted podría intentar algo así como la actualización del BindingContext y cambiando la ModelType a StandardProfile y luego llamar

bindingContext.ModelType = typeof(StandardProfile); 
IProfile profile = base.BindModel(controllerContext, bindingContext); 

De todas formas, tener una interfaz de vacío es raro ~


Editar: sólo quieren añadir ese código anterior es solo un pseudo código, necesitaría marcar DefaultModelBinder para ver exactamente lo que quiere escribir.


Edición # 2:

Puede usted hacer:

public class ProfileModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { 
    { 
     bindingContext.ModelType = typeof(StandardProfile); 
     return base.BindModel(controllerContext, bindingContext); 
    } 
} 

No hay necesidad de hacer una carpeta de modelo para AccountView, que uno trabaja muy bien.


Edición # 3

Probado a cabo, el aglutinante anterior funciona, sólo tiene que añadir:

ModelBinders.Binders[typeof(IProfile)] = new ProfileModelBinder(); 

Su acción se ve así:

public ActionResult AddAccount(AccountViewModel viewModel) { 
    // viewModel is fully populated, including profile, don't call UpdateModel 
} 

Se puede utilizar IOC al configurar el encuadernador del modelo (se inyectó el constructor del tipo, por ejemplo).

+0

La interfaz vacía me permite reutilizar el mismo código de código central en cada sitio en el que lo uso, al mismo tiempo que proporciono un tipo de perfil diferente con un IOC. Es posible que pueda probar una clase base en lugar de una interfaz para este propósito, pero no estoy seguro de qué más puedo hacer que me brinde la flexibilidad que estoy buscando. Veré el ModelType que has mencionado. –

+0

Una clase base vacía tampoco te servirá de mucho. Entonces, en su código para el método CreateModel, usted está llamando algo así como: IoC.GetInstance () que ha conectado para devolver un nuevo StandardProfile Interesante :). Todavía no estoy seguro de cuánta reutilización de código puede obtener cuando todo lo que usa IProfile tiene que emitirlo primero a la clase correcta, pero ya ... creo que la especificación del Tipo en el contexto vinculante funcionará. – anonymous

+0

La reutilización proviene del hecho de que muchos de los controladores de mi sitio se encuentran en un conjunto común al que hago referencia. Cada sitio que construyo hace referencia a este conjunto común para controladores y modelos. Luego puedo agregar controladores adicionales, modelos, vistas, etc. específicos para cada sitio. En este caso, necesitaba poder definir campos de perfil completamente diferentes sitio por sitio. De ahí la necesidad de solo una interfaz aquí. –

0

No inspeccionar el tipo real detrás de la interfaz se discutió aquí: http://forums.asp.net/t/1348233.aspx

Dicho esto, he encontrado una manera hacker alrededor del problema. Como ya tenía un archivador de modelo personalizado para este tipo, pude agregarle un código para realizar el enlace.Esto es lo que mi carpeta modelo se parece ahora:

public class AccountViewModelModelBinder : DefaultModelBinder 
{ 
    private readonly IProfileViewModel profileViewModel; 
    private bool profileBound = false; 

    public AccountViewModelModelBinder(IProfileViewModel profileViewModel) 
    { 
     this.profileViewModel = profileViewModel; 
    } 

    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     // Bind the profile 
     if (profileBound) 
      return; 

     profileBound = true; 

     bindingContext.ModelType = profileViewModel.GetType(); 
     bindingContext.Model = profileViewModel; 
     bindingContext.ModelName = "Profile"; 

     BindModel(controllerContext, bindingContext); 
    } 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType) 
    { 
     var model = new AccountViewModel(); 
     model.Profile = profileViewModel; 

     return model; 
    } 
} 

Básicamente, cuando el aglutinante modelo se "hace" la unión del AccountViewModel principal, a continuación, alterar el contexto de la unión (como lo sugiere Eyston) y llamo bindModel una vez más. Esto entonces une mi perfil. Tenga en cuenta que llamé a GetType en el profileViewModel (que es proporcionado por el contenedor de IOC en el constructor). También observe que incluyo una bandera para indicar si el modelo de perfil ya se ha vinculado. De lo contrario, se produciría un ciclo interminable de OnModelUpdated.

No estoy diciendo que esto sea bonito, pero funciona bastante bien para mis necesidades. Todavía me encantaría escuchar sobre otras sugerencias.

+0

ver Edición # 2/# 3. – anonymous