使用 Structuremap 装饰通用界面

发布于 2024-12-13 02:17:17 字数 3709 浏览 0 评论 0原文

我有一个通用接口,它接受两种通用类型。我想装饰返回的所有版本,但由于我不知道调用 EnrichWith 时的类型,因此它显然无法编译。我尝试使用在上下文中传递的 EnrichWith 重载,认为也许我可以获取传入的泛型类型并调用 Activator.CreateInstance,但是在调试和检查它时上下文没有任何有用的信息。

这是我到目前为止所拥有的。这是我的通用接口:

public interface IServiceOperation<in TRequest, out TResponse> where TResponse : ServiceResult, new()
{
    TResponse PerformService(TRequest validatedRequest);
}

这是一个示例实现:

public class SignUpService : IServiceOperation<SignUpRequest, SignUpResult>
{
    private readonly IUserRepository _userRepo;

    public SignUpService(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public SignUpResult PerformService(SignUpRequest validatedRequest)
    {
        var user = Mapper.Map<User>(validatedRequest);

        user.MarkAsLoggedIn();
        user.ChangePassword(validatedRequest.UnhashedPassword);

        using(var transaction = _userRepo.BeginTransaction())
        {
            _userRepo.Save(user);
            transaction.Commit();
        }

        return new SignUpResult();
    }
}

这是我的装饰器,它还接受另一个服务:

public class ValidateServiceDecorator<TRequest, TResponse> : IServiceOperation<TRequest, TResponse> where TResponse : ServiceResult, new()
{
    private readonly IServiceOperation<TRequest, TResponse> _serviceOperation;
    private readonly IValidationService _validationService;

    public ValidateServiceDecorator(IServiceOperation<TRequest, TResponse> serviceOperation,
        IValidationService validationService)
    {
        _serviceOperation = serviceOperation;
        _validationService = validationService;
    }

    public TResponse PerformService(TRequest request)
    {
        var response = new TResponse();
        var validationResult = _validationService.Validate(request);

        if (!validationResult.IsValid)
        {
            response.ValidationErrors = validationResult.ValidationErrors;
            return response;
        }

        return _serviceOperation.PerformService(request);
    }

最后,这是我在容器上的进展情况。这显然无法编译,但 EnrichWith 行显示了我想要实现的目标:

public class StructureMapServiceScanner : Registry
{
    public StructureMapServiceScanner()
    {
        Scan(scanner =>
                {
                    scanner.AssemblyContainingType(typeof (IServiceOperation<,>));
                    scanner.ConnectImplementationsToTypesClosing(typeof (IServiceOperation<,>));
                });

        For(typeof (IServiceOperation<,>))
        .EnrichWith((ioc, original) => new ValidateServiceDecorator(original, ioc.GetInstance<IValidationService>()));
    }
}

并且只是因为这个问题需要更多代码,这是我想要通过的测试:

[TestClass]
public class StructureMapServiceScannerSpecs
{
    [TestMethod]
    public void Test()
    {
        ObjectFactory.Configure(cfg =>
                                    {
                                        cfg.AddRegistry<StructureMapServiceScanner>();
                                        cfg.For<IUserRepository>().Use(new Mock<IUserRepository>().Object);
                                        cfg.For<IValidationService>().Use(new Mock<IValidationService>().Object);
                                    });

        var service = ObjectFactory.GetInstance<IServiceOperation<SignUpRequest, SignUpResult>>();

        service.ShouldNotBeNull();
        service.ShouldBeType<ValidateServiceDecorator<SignUpRequest, SignUpResult>>();
    }
}

我觉得这是一些东西这应该很简单,而且我真的缺少一些如何使用 StructureMap 的东西。我可以为请求和响应类型的所有组合创建特定于类型的版本,但显然这是不可取的。那么我错过了什么?

I have a generic interface, that takes in two generic types. I want to decorate all versions returned, but since I don't know the type when calling EnrichWith, it obviously doesn't compile. I've tried using the EnrichWith overload that passes in the context, thinking maybe I could grab the generic types passed in and call Activator.CreateInstance, but the context doesn't have any useful information on it when debugging and inspecting it.

Here's what I have so far. This is my generic interface:

public interface IServiceOperation<in TRequest, out TResponse> where TResponse : ServiceResult, new()
{
    TResponse PerformService(TRequest validatedRequest);
}

Here's a sample implementation:

public class SignUpService : IServiceOperation<SignUpRequest, SignUpResult>
{
    private readonly IUserRepository _userRepo;

    public SignUpService(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public SignUpResult PerformService(SignUpRequest validatedRequest)
    {
        var user = Mapper.Map<User>(validatedRequest);

        user.MarkAsLoggedIn();
        user.ChangePassword(validatedRequest.UnhashedPassword);

        using(var transaction = _userRepo.BeginTransaction())
        {
            _userRepo.Save(user);
            transaction.Commit();
        }

        return new SignUpResult();
    }
}

Here is my decorator, that takes in another service as well:

public class ValidateServiceDecorator<TRequest, TResponse> : IServiceOperation<TRequest, TResponse> where TResponse : ServiceResult, new()
{
    private readonly IServiceOperation<TRequest, TResponse> _serviceOperation;
    private readonly IValidationService _validationService;

    public ValidateServiceDecorator(IServiceOperation<TRequest, TResponse> serviceOperation,
        IValidationService validationService)
    {
        _serviceOperation = serviceOperation;
        _validationService = validationService;
    }

    public TResponse PerformService(TRequest request)
    {
        var response = new TResponse();
        var validationResult = _validationService.Validate(request);

        if (!validationResult.IsValid)
        {
            response.ValidationErrors = validationResult.ValidationErrors;
            return response;
        }

        return _serviceOperation.PerformService(request);
    }

Lastly, here is how far I've gotten on my container. This obviously doesn't compile, but the EnrichWith line shows what I'm trying to achieve:

public class StructureMapServiceScanner : Registry
{
    public StructureMapServiceScanner()
    {
        Scan(scanner =>
                {
                    scanner.AssemblyContainingType(typeof (IServiceOperation<,>));
                    scanner.ConnectImplementationsToTypesClosing(typeof (IServiceOperation<,>));
                });

        For(typeof (IServiceOperation<,>))
        .EnrichWith((ioc, original) => new ValidateServiceDecorator(original, ioc.GetInstance<IValidationService>()));
    }
}

And just because this question needed a little more code, here's my test that I'm trying to get to pass:

[TestClass]
public class StructureMapServiceScannerSpecs
{
    [TestMethod]
    public void Test()
    {
        ObjectFactory.Configure(cfg =>
                                    {
                                        cfg.AddRegistry<StructureMapServiceScanner>();
                                        cfg.For<IUserRepository>().Use(new Mock<IUserRepository>().Object);
                                        cfg.For<IValidationService>().Use(new Mock<IValidationService>().Object);
                                    });

        var service = ObjectFactory.GetInstance<IServiceOperation<SignUpRequest, SignUpResult>>();

        service.ShouldNotBeNull();
        service.ShouldBeType<ValidateServiceDecorator<SignUpRequest, SignUpResult>>();
    }
}

I feel like this is something that should be simple, and I'm really missing something with how to use StructureMap. I could create type-specific versions for all combinations of Request and Response types, but obviously that's not desirable. So what am I missing?

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

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

发布评论

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

评论(2

偏爱你一生 2024-12-20 02:17:17

最终能够弄清楚。我创建了一个注册约定:

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var interfacesImplemented = type.GetInterfaces();

        foreach (var interfaceImplemented in interfacesImplemented)
        {
            if (interfaceImplemented.IsGenericType && interfaceImplemented.GetGenericTypeDefinition() == typeof(IServiceOperation<,>))
            {
                var genericParameters = interfaceImplemented.GetGenericArguments();
                var closedValidatorType = typeof(ValidateServiceDecorator<,>).MakeGenericType(genericParameters);

                registry.For(interfaceImplemented)
                    .EnrichWith((context, original) => Activator.CreateInstance(closedValidatorType, original,
                                                                                context.GetInstance<IValidationService>()));
            }
        }
    }
}

Was able to figure it out, eventually. I created a RegistrationConvention:

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var interfacesImplemented = type.GetInterfaces();

        foreach (var interfaceImplemented in interfacesImplemented)
        {
            if (interfaceImplemented.IsGenericType && interfaceImplemented.GetGenericTypeDefinition() == typeof(IServiceOperation<,>))
            {
                var genericParameters = interfaceImplemented.GetGenericArguments();
                var closedValidatorType = typeof(ValidateServiceDecorator<,>).MakeGenericType(genericParameters);

                registry.For(interfaceImplemented)
                    .EnrichWith((context, original) => Activator.CreateInstance(closedValidatorType, original,
                                                                                context.GetInstance<IValidationService>()));
            }
        }
    }
}
挖个坑埋了你 2024-12-20 02:17:17

这是一种仍然利用 StructureMap 的 IoC 功能的方法,允许将附加服务轻松注入到装饰器中。它并不完美,因为它假设您使用的是主容器而不是子容器,但它可能适用于大多数情况。

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var handlerInterfaces = (from t in type.GetInterfaces()
                                 where t.IsGenericType &&
                                       t.GetGenericTypeDefinition() == typeof (IHandle<,>)
                                 select t);

        foreach (var handler in handlerInterfaces)
        {
            var decoratorType = typeof (ValidationDecorator<,>).MakeGenericType(handler.GetGenericArguments());

            registry.For(handler)
                .EnrichWith((ctx, orig) => ObjectFactory.With(handler, orig).GetInstance(decoratorType));
        }
    }
}

理想情况下,StructureMap 的 IContext 应该像 IContainer 一样公开 With 方法。如果没有这一点,这个问题就没有一个很好的解决方案。

Here's an approach that still leverages StructureMap's IoC capabilities, allowing additional services to be injected easily into your decorator. It's not perfect since it assumes you are using the primary container and not a child container, but it will probably work for most scenarios.

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var handlerInterfaces = (from t in type.GetInterfaces()
                                 where t.IsGenericType &&
                                       t.GetGenericTypeDefinition() == typeof (IHandle<,>)
                                 select t);

        foreach (var handler in handlerInterfaces)
        {
            var decoratorType = typeof (ValidationDecorator<,>).MakeGenericType(handler.GetGenericArguments());

            registry.For(handler)
                .EnrichWith((ctx, orig) => ObjectFactory.With(handler, orig).GetInstance(decoratorType));
        }
    }
}

Ideally, StructureMap's IContext should expose the With method just like IContainer does. Without that, there's not really a great solution to this problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文