使用 Autofac 将服务注入基类

发布于 2024-09-30 14:56:02 字数 3232 浏览 4 评论 0原文

TL;DR:当两个自定义 ModelBinder 实现都依赖 Autofac 向其中注入(公共)依赖项时,如何将两个自定义 ModelBinder 实现共享的逻辑合并到单个基类中?


在查看我正在处理的 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 注入基类构造函数,同时强制派生类提供要编码的属性名称?

TL;DR: How can I consolidate logic shared by two custom ModelBinder implementations into a single base class, when both implementations rely on Autofac to inject a (common) dependency into them?


While reviewing some code in an ASP.NET MVC project I'm working on, I realized that I have two custom model binders that essentially do they same thing. They both inherit from DefaultModelBinder, and they both encode a single property on two separate view model classes, using an IEncodingService that is injected into their constructors.

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

I wrote a generic base class for both of these classes to inherit from:

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

Using Autofac, how would I inject the IEncodingService into the base class constructor, while forcing derived classes to provide the name of the property to encode?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情仇皆在手 2024-10-07 14:56:02

实际上,我的处理方法略有不同,倾向于组合而不是继承。这意味着我将封装属性操作的细节,并将不同的实现传递给单个模型绑定器。

首先,定义一个表示绑定单个属性的接口:

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");

I would actually approach this slightly differently, by favoring composition over inheritance. This means I would encapsulate the details of the property manipulation, and pass different implementations to a single model binder.

First, define an interface which represents binding a single property:

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

Then, implement it using the parameters originally from 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);
        }
    }
}

Next, implement EncodedPropertyModelBinder using the new interface:

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

Finally, register two versions of the view model using Autofac named instances, passing in different configurations of 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");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文