Внедрение службы в базовый класс с использованием Autofac - PullRequest
2 голосов
/ 03 ноября 2010

TL; DR: Как можно объединить логику, совместно используемую двумя пользовательскими реализациями ModelBinder, в один базовый класс, когда обе реализации полагаются на Autofac для введения в них (общей) зависимости?


При просмотре некоторого кода в проекте ASP.NET MVC, над которым я работаю, я понял, что у меня есть два пользовательских механизма связывания моделей, которые по сути делают одно и то же.Они оба наследуются от DefaultModelBinder и кодируют одно свойство в двух отдельных классах модели представления, используя IEncodingService, который вставляется в их конструкторы.

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;
    }
}

Я написал базовый базовый класс дляоба эти класса наследуются от:

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;
    }
}

Используя Autofac, как бы я внедрил IEncodingService в конструктор базового класса, в то же время заставляя производные классы предоставлять имя свойства для кодирования?

1 Ответ

3 голосов
/ 04 ноября 2010

Я бы на самом деле подошел к этому немного по-другому, отдавая предпочтение композиции вместо наследования . Это означает, что я бы инкапсулировал детали манипулирования свойством и передавал различные реализации в один связыватель модели.

Сначала определите интерфейс, который представляет привязку одного свойства:

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

Затем реализуйте его, используя параметры, изначально взятые из 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);
        }
    }
}

Затем внедрите EncodedPropertyModelBinder, используя новый интерфейс:

public class EncodedPropertyModelBinder : DefaultModelBinder
{
    private readonly IPropertyBinder _propertyBinder;

    public EncodedPropertyModelBinder(IPropertyBinder propertyBinder)
    {
        _propertyBinder = propertyBinder;
    }

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

        if(model != null)
        {
            _propertyBinder.SetPropertyValue(model, bindingContext);
        }

        return model;
    }
}

Наконец, зарегистрируйте две версии модели представления, используя именованные экземпляры Autofac, передавая в различных конфигурациях 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");
...