2010-11-03 50 views
2

TL; DR: Comment consolider la logique partagée par deux implémentations ModelBinder personnalisées dans une seule classe de base, lorsque les deux implémentations s'appuient sur Autofac pour leur injecter une dépendance (commune)? ?Injection d'un service dans une classe de base avec Autofac


Lors de l'examen du code dans un projet ASP.NET MVC je travaille, je réalisais que j'ai deux classeurs de modèles personnalisés qui font essentiellement ils même chose. Ils héritent tous les deux de DefaultModelBinder, et ils codent tous les deux une seule propriété sur deux classes de modèles de vues distinctes, en utilisant un IEncodingService qui est injecté dans leurs constructeurs.

public class ResetQuestionAndAnswerViewModelBinder : DefaultModelBinder { 
    public ResetQuestionAndAnswerViewModelBinder(IEncodingService encodingService) { 
     encoder = encodingService; 
    } 

    private readonly IEncodingService encoder; 

    public override object BindModel(ControllerContext controllerContext, 
            ModelBindingContext bindingContext) { 
     var model = base.BindModel(controllerContext, bindingContext) as ResetQuestionAndAnswerViewModel; 

     if (model != null) { 
      var answer = bindingContext.ValueProvider.GetValue("Answer"); 

      if ((answer != null) && !(answer.AttemptedValue.IsNullOrEmpty())) { 
       model.Answer = encoder.Encode(answer.AttemptedValue); 
      } 
     } 

     return model; 
    } 
} 

public class ConfirmIdentityViewModelBinder : DefaultModelBinder { 
    public ConfirmIdentityViewModelBinder(IEncodingService encodingService) { 
     encoder = encodingService; 
    } 

    private readonly IEncodingService encoder; 

    public override object BindModel(ControllerContext controllerContext, 
            ModelBindingContext bindingContext) { 
     var model = base.BindModel(controllerContext, bindingContext) as ConfirmIdentityViewModel; 

     if (model != null) { 
      var secretKey = bindingContext.ValueProvider.GetValue("SecretKey"); 

      if ((secretKey != null) && !(secretKey.AttemptedValue.IsNullOrEmpty())) { 
       model.SecretKeyHash = encoder.Encode(secretKey.AttemptedValue); 
      } 
     } 

     return model; 
    } 
} 

j'ai écrit une classe de base générique pour ces deux classes pour hériter de:

public class EncodedPropertyModelBinder<TViewModel> : DefaultModelBinder 
    where TViewModel : class { 

    public EncodedPropertyModelBinder(IEncodingService encodingService, 
             string propertyName) { 
     encoder = encodingService; 
     property = propertyName; 
    } 

    private readonly IEncodingService encoder; 
    private readonly string property; 

    public override object BindModel(ControllerContext controllerContext, 
            ModelBindingContext bindingContext) { 
     var model = base.BindModel(controllerContext, bindingContext) as TViewModel; 

     if (model != null) { 
      var value = bindingContext.ValueProvider.GetValue(property); 

      if ((value != null) && !(value.AttemptedValue.IsNullOrEmpty())) { 
       var encodedValue = encoder.Encode(value.AttemptedValue); 

       var propertyInfo = model.GetType().GetProperty(property); 
       propertyInfo.SetValue(model, encodedValue, null); 
      } 
     } 

     return model; 
    } 
} 

En utilisant Autofac, comment pourrais-je injecter le IEncodingService dans le constructeur de la classe de base, tout en forçant les classes dérivées à fournir le nom de la propriété à encoder?

Répondre

3

Je voudrais effectivement aborder ce légèrement différemment, par favoring composition over inheritance. Cela signifie que j'encapsulerais les détails de la manipulation de propriété, et passerais différentes implémentations à un seul classeur de modèle.

D'abord, définir une interface qui représente la liaison d'une propriété unique:

public interface IPropertyBinder 
{ 
    void SetPropertyValue(object model, ModelBindingContext context); 
} 

Ensuite, mettre en œuvre à l'aide des paramètres à l'origine de EncodedPropertyModelBinder:

public sealed class PropertyBinder : IPropertyBinder 
{ 
    private readonly IEncodingService _encodingService; 
    private readonly string _propertyName; 

    public PropertyBinder(IEncodingService encodingService, string propertyName) 
    { 
     _encodingService = encodingService; 
     _propertyName = propertyName; 
    } 

    public void SetPropertyValue(object model, ModelBindingContext bindingContext) 
    { 
     var value = bindingContext.ValueProvider.GetValue(_propertyName); 

     if(value != null && !value.AttemptedValue.IsNullOrEmpty()) 
     { 
      var encodedValue = _encodingService.Encode(value.AttemptedValue); 

      var property = model.GetType().GetProperty(_propertyName); 

      property.SetValue(model, encodedValue, null); 
     } 
    } 
} 

Ensuite, mettre en œuvre EncodedPropertyModelBinder en utilisant la nouvelle interface:

Enfin, enregistrer deux versions du modèle de vue à l'aide d'instances nommées Autofac, en passant dans différentes configurations de PropertyBinder:

builder. 
    Register(c => new EncodedPropertyModelBinder(new PropertyBinder(c.Resolve<IEncodingService>(), "Answer"))) 
    .Named<EncodedPropertyModelBinder>("AnswerBinder"); 

builder. 
    Register(c => new EncodedPropertyModelBinder(new PropertyBinder(c.Resolve<IEncodingService>(), "SecretKey"))) 
    .Named<EncodedPropertyModelBinder>("SecretKeyBinder");