8

Digamos que tengo una entidad de usuario y me gustaría establecer su propiedad CreationTime en el constructor en DateTime.Now. Pero ser una entidad que adopta prueba de la unidad no quiero acceder a la DateTime.Now directamente, sino utilizar un ITimeProvider:¿Cómo usar un contenedor DI/IoC con el archivador modelo en ASP.NET MVC 2+?

public class User { 
    public User(ITimeProvider timeProvider) { 
     // ... 
     this.CreationTime = timeProvider.Now; 
    } 

    // ..... 
} 

public interface ITimeProvider { 
    public DateTime Now { get; } 
} 

public class TimeProvider : ITimeProvider { 
    public DateTime Now { get { return DateTime.Now; } } 
} 

estoy usando ninject 2 en mi aplicación ASP.NET MVC 2.0. Tengo un UserController y dos métodos Create (uno para GET y otro para POST). El de GET es directo, pero el de POST no es tan directo y no es tan avanzado: P porque tengo que meterme con el encuadernador de modelos para decirle que obtenga una referencia de una implementación de ITimeProvider para poder construir una instancia de usuario.

public class UserController : Controller { 

    [HttpGet] 
    public ViewResult Create() { 
     return View(); 
    } 

    [HttpPost] 
    public ActionResult Create(User user) { 

     // ... 

    } 
} 

También me gustaría poder mantener todas las características del encuadernador de modelo predeterminado.

¿Alguna posibilidad de resolver esto simple/elegante/etc.? : D

+0

relacionado: Tema específico de Ninject http://stackoverflow.com/questions/9186560/ninject-inject-a-dependency-into-a-custom-model-binder-and-using-inresquestscope –

Respuesta

5

¿Qué tal en lugar de utilizar un ITimeProvider intente esto:

public class User 
{ 
    public Func<DateTime> DateTimeProvider =() => DateTime.Now; 

    public User() 
    { 
     this.CreationTime = DateTimeProvider(); 
    } 
} 

Y en su unidad de prueba:

var user = new User(); 
user.DateTimeProvider =() => new DateTime(2010, 5, 24); 

sé que esto no es muy elegante, pero en lugar de jugar con el carpeta modelo esto podría ser una solución. Si esto no se siente como una buena solución, podría implementar un encuadernador de modelo personalizado y anular el método CreateModel donde inyectaría las dependencias en el constructor del modelo.

+0

Sí, esto podría funcionar. Una variante tuya podría ser un constructor predeterminado como el siguiente: User(): this (TimeProvider.Instance) {} Aún esperaba una forma simple de insertar un [algo] entre el archivador DefaultModel y ASP.NET MVC 2 Framework .. –

+2

Bueno, entonces anulando el 'DefaultModelBinder' es la ruta que debes tomar. –

1

Otra opción es crear una clase diferente para representar a los usuarios que aún no se han conservado pero que no tienen ninguna propiedad de fecha de creación.

Incluso si CreationDate es uno de User 's invariantes, puede ser anulable en su modelo de vista - y puede establecerlo más abajo en su controlador o capa de dominio.

Después de todo, probablemente no importe, pero ¿el atributo fecha de creación realmente representa el momento en que construye una instancia de usuario, o sería más apropiado que represente el momento en que un usuario envía sus datos?

20

Un par de observaciones:

No se inyecte dependencias sólo para consultarlos en el constructor

No hay razón para inyectar un ITimeProvider en un solo usuario para invocar Now inmediatamente. Sólo inyectar el tiempo de creación directa en su lugar:

public User(DateTime creationTime) 
{ 
    this.CreationTime = creationTime; 
} 

Una muy buena regla general relacionada con DI es que constructores deben realizar ninguna lógica.

No utilice DI con ModelBinders

Un ASP.NET MVC ModelBinder es un lugar muy pobre para hacer DI, sobre todo porque no se puede utilizar Constructor de inyección. La única opción restante es static Service Locator anti-pattern.

Un ModelBinder traduce la información HTTP GET y POST a un objeto fuertemente tipado, pero conceptualmente estos tipos no son objetos de dominio, pero similar a Data Transfer Objects.

Una solución mucho mejor para ASP.NET MVC es renunciar a ModelBinders personalizados por completo y en su lugar abrazar de forma explícita que lo que recibe de la conexión HTTP es no es su objeto de dominio completo.

Usted puede tener una búsqueda simple o mapeador para recuperar el objeto de dominio en el controlador:

public ActionResult Create(UserPostModel userPost) 
{ 
    User u = this.userRepository.Lookup(userPost); 
    // ... 
} 

donde this.userRepository es una dependencia inyectado.

+0

"Una solución mucho mejor para ASP.NET MVC es renunciar por completo a los ModelBinders personalizados y, en cambio, aceptar explícitamente que lo que recibe de la conexión HTTP no es su objeto de dominio completo". Ahora que es un pensamiento interesante. ¿Qué sucede si tenemos varias entidades con propiedades comunes como la fecha de creación en el ejemplo anterior y quisiéramos establecerlas todas en una carpeta para todas nuestras entidades (tenemos todas las entidades heredando una clase de entidad Base y aquí es donde se adjunta la carpeta) ? En ese caso, ¿deshacerse de las carpetas daría como resultado la duplicación del código en cada Create en cada controlador? – mare

+2

El punto es que no importa. No recibe una Entidad, recibe un HTTP POST (o GET). Abrace el protocolo. Hay muchas otras maneras de evitar la duplicación de código. –

+2

Como asp.net mvc 3 tiene soporte para imodelbinderprovider, muestra que estaban pensando en las líneas correctas, por lo tanto, no estoy de acuerdo con tus comentarios, pero entiendo de dónde vienes. Otra opción podría ser crear un controlador base que maneje la lógica repetitiva ... – Haroon

Cuestiones relacionadas