8

Disons que j'ai une entité utilisateur et que je voudrais définir sa propriété CreationTime dans le constructeur à DateTime.Now. Mais être un adopteur de test unitaire Je ne veux pas accéder directement à DateTime.Now mais utiliser un ITimeProvider:Comment utiliser un conteneur DI/IoC avec le classeur de modèle dans 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; } } 
} 

J'utilise ninject 2 dans mon application ASP.NET MVC 2.0. J'ai un UserController et deux méthodes Create (une pour GET et une pour POST). Celui pour GET est simple mais celui pour POST n'est pas si direct et pas si avancé: P parce que j'ai besoin de jouer avec le classeur modèle pour lui dire d'obtenir une référence d'implémentation de ITimeProvider afin de pouvoir construire une instance d'utilisateur. Je voudrais également pouvoir conserver toutes les fonctionnalités du classeur par défaut.

Une chance de résoudre ce simple/élégant/etc? : D

+0

en relation: Ninject thread spécifique http://stackoverflow.com/questions/9186560/ninject-inject-a-dependency-into-a-custom-model-binder-and-using-inrequestscope –

Répondre

5

Que diriez-vous au lieu d'utiliser un ITimeProvider essayer:

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

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

Et dans votre unité d'essai:

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

Je sais que ce n'est pas très élégante mais au lieu de jouer avec la modèle liant cela pourrait être une solution. Si cela ne vous semble pas être une bonne solution, vous pouvez implémenter un classeur de modèle personnalisé et remplacer la méthode CreateModel où vous souhaitez injecter les dépendances dans le constructeur du modèle.

+0

Oui, cela pourrait fonctionner. Une variante de la vôtre pourrait être un constructeur par défaut comme: User(): this (TimeProvider.Instance) {} Encore j'espérais un moyen simple d'injecter un [quelque chose] entre le classeur DefaultModel et le ASP.NET MVC 2 Framework .. –

+2

Eh bien, le fait de surcharger 'DefaultModelBinder' est la route que vous devrez prendre. –

1

Une autre option consiste à créer une classe différente pour représenter les utilisateurs qui n'ont pas encore été conservés et qui n'ont aucune propriété de date de création.

Même si CreationDate est l'un des invariants de User, il peut être nullable dans votre modèle de vue - et vous pouvez le définir plus en aval, dans votre contrôleur ou couche de domaine. Après tout, cela n'a probablement pas d'importance, mais l'attribut de date de création devrait-il représenter le moment où vous construisez une instance d'utilisateur, ou serait-il plus approprié qu'il représente le moment où un utilisateur soumet ses données?

20

Quelques observations:

Ne pas injecter les dépendances juste pour les interroger dans le constructeur

Il n'y a aucune raison d'injecter un ITimeProvider dans un utilisateur juste pour appeler Now immédiatement. Il suffit d'injecter le temps de création directement au lieu:

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

Une très bonne règle de base en rapport avec DI est que constructeurs doivent effectuer aucune logique.

Ne pas utiliser DI avec ModelBinders

Un ASP.NET MVC ModelBinder est un endroit vraiment pauvre pour faire DI, en particulier parce que vous ne pouvez pas utiliser l'injection de constructeur. La seule option restante est la static Service Locator anti-pattern.

Un ModelBinder se traduit par HTTP GET et POST informations sur un objet fortement typé, mais sur le plan conceptuel ces types ne sont pas des objets de domaine, mais semblable à Data Transfer Objects.

Une bien meilleure solution pour ASP.NET MVC est de renoncer ModelBinders personnalisés complètement et au lieu embrasser explicitement que ce que vous recevez de la connexion HTTP est pas votre objet de domaine complet.

Vous pouvez avoir une simple recherche ou mappeur pour récupérer votre objet de domaine dans votre contrôleur:

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

this.userRepository est une dépendance injectée.

+0

"Une meilleure solution pour ASP.NET MVC est de renoncer complètement à ModelBinders personnalisés et d'accepter explicitement que ce que vous recevez de la connexion HTTP ne soit pas votre objet de domaine complet." Maintenant c'est une pensée intéressante. Et si nous avions plusieurs entités avec des propriétés communes comme la date de création dans l'exemple ci-dessus et que nous voulions tous les classer dans un classeur pour toutes nos entités (nous avons toutes les entités héritant d'une classe d'entité Base). ? Dans ce cas, le vidage des classeurs entraînerait une duplication de code dans chaque création de chaque contrôleur? – mare

+2

Le fait est que cela n'a pas d'importance. Vous ne recevez pas une entité, vous recevez un HTTP POST (ou GET). Embrassez le protocole. Il existe de nombreux autres moyens d'éviter la duplication de code. –

+2

Comme asp.net mvc 3 a un support pour imodelbinderprovider, il montre qu'ils pensaient dans le bon sens, donc je ne suis pas d'accord avec vos commentaires mais comprenez d'où vous venez. Une autre option pourrait être de créer un contrôleur de base qui gère la logique répétitive ... – Haroon