简体   繁体   English

StructureMap通过注入而不是服务位置来解析依赖性

[英]StructureMap resolve dependency through injection instead of service location

In my project I register many ISerializers implementations with the assembly scanner. 在我的项目中,我使用汇编扫描程序注册了许多ISerializers实现。 FWIW this is the code that registers my ISerializers FWIW这是注册我的ISerializers的代码

Scan(scanner =>
{
    scanner.AssemblyContainingType<ISerializer>();
    scanner.AddAllTypesOf<ISerializer>().NameBy(type => type.Name);
    scanner.WithDefaultConventions();
});

Which then correctly registers 然后正确注册

ISerializer (...ISerializer)
Scoped as:  Transient

JsonSerializer    Configured Instance of ...JsonSerializer
BsonSerializer    Configured Instance of ...BsonSerializer

And so forth. 等等。

Currently the only way I've been able to figure out how to resolve the serializer I want is to hardcode a service location call with 目前,我能够弄清楚如何解决我想要的串行器的唯一方法是使用硬编码服务位置调用

jsonSerializer = ObjectFactory.GetNamedInstance<ISerializer>("JsonSerializer");

Now I know in my class that I specifically want the jsonSerializer so is there a way to configure a rule or similar that says for ISerializer's to connect the named instance based on the property name? 现在我在课堂上知道我特别想要jsonSerializer,所以有没有办法配置一个规则或类似的东西,让ISerializer根据属性名连接命名实例? So that I could have 所以我可以

MySomeClass(ISerializer jsonSerializer, ....)

And StructureMap correctly resolve this scenario? StructureMap正确解决了这种情况? Or am I approaching this wrong and perhaps I should just register the concrete type that implements ISerializer and then just specifically use 或者我接近这个错误,也许我应该只注册实现ISerializer的具体类型,然后专门使用

MySomeClass(JsonSerializer jsonSerializer, ....)

for something along these lines with the concrete class? 对于具体类的这些方面的东西?

When you're doing Dependency Injection and need to be able to create specially-typed instances of a given interface, the recommended solution is to create specialized factory classes. 当您执行依赖注入并且需要能够创建给定接口的特殊类型实例时,建议的解决方案是创建专用工厂类。 This allows you to use a named argument without actually injecting the container. 这允许您在不实际注入容器的情况下使用命名参数。

Example

This is the abstract type that you'll be injecting: 这是您要注入的抽象类型:

public interface ISerializerFactory
{
    ISerializer GetSerializer(string name);
}

Here is the concrete type, which makes use of your container (StructureMap): 这是具体类型,它使用您的容器(StructureMap):

public class StructureMapSerializerFactory : ISerializerFactory
{
    public ISerializer GetSerializer(string name)
    {
        return ObjectFactory.GetNamedInstance<ISerializer>(name);
    }
}

Then your class would look like the following: 然后你的课将如下所示:

public class MyClass
{
    private readonly ISerializerFactory serializerFactory;

    public MyClass(ISerializerFactory serializerFactory)
    {
        if (serializerFactory == null)
            throw new ArgumentNullException("serializerFactory");
        this.serializerFactory = serializerFactory;
    }

    public string SerializeSomeData(MyData data)
    {
        ISerializer serializer = serializerFactory.GetSerializer("Json");
        return serializer.Serialize(data);
    }
}

I've written this passing "Json" instead of "JsonSerializer" which won't automatically work. 我写过这个传递“Json”而不是“JsonSerializer”,它不会自动运行。 But I think you should change your registration names to eliminate the redundant "Serializer" suffix (we already know it's a serializer because we're asking for an ISerializer ). 但我认为您应该更改您的注册名称以消除冗余的“Serializer”后缀(我们已经知道它是一个序列化器,因为我们要求使用ISerializer )。 In other words create a method like this: 换句话说,创建一个这样的方法:

private static string ExtractSerializerName(Type serializerType)
{
    string typeName = serializerType.Name;
    int suffixIndex = typeName.IndexOf("Serializer");
    return (suffixIndex >= 0) ?
        typeName.Substring(0, suffixIndex - 1) : typeName;
}

And register it like this: 并注册如下:

scanner.AddAllTypesOf<ISerializer>().NameBy(type => ExtractSerializerName(type));

Then you can just use the string "Json" to create it instead of "JsonSerializer", which will look a little less ugly and feel less coupled. 然后你可以使用字符串“Json”来创建它而不是“JsonSerializer”,它看起来会有点不那么难看,感觉不那么耦合。

If you don't like the hard-coded strings, then another thing you can do is create an enumeration for your factory: 如果您不喜欢硬编码的字符串,那么您可以做的另一件事是为您的工厂创建一个枚举:

public enum SerializationFormat { Json, Bson, Xml };

public interface ISerializerFactory
{
    ISerializer GetSerializer(SerializationFormat format);
}

public class StructureMapSerializerFactory : ISerializerFactory
{
    public ISerializer GetSerializer(SerializationFormat format)
    {
        return ObjectFactory.GetNamedInstance<ISerializer>(format.ToString());
    }
}

So instead of writing this: 所以不要写这个:

ISerializer serializer = serializerFactory.GetSerializer("Json");

You get to write this instead: 你可以这样写:

ISerializer serializer =
    serializerFactory.GetSerializer(SerializationFormat.Json);

Which is going to be less error-prone in the long run. 从长远来看,哪个更容易出错。

This will probably be more maintainable in the long run because if you start changing the class names of your serializers and/or the names are inconsistent, then you can replace the simple ToString() with a switch statement and actually map the enum values to the class names you're registering. 从长远来看,这可能更容易维护,因为如果你开始更改序列化程序的类名和/或名称不一致,那么你可以用switch语句替换简单的ToString()并实际将枚举值映射到您正在注册的班级名称。

I'd probably put all of this code - including the auto-registration code in your question - in the same namespace, or even the same code file, to clearly indicate that these pieces are all interdependent. 我可能会将所有这些代码 - 包括你的问题中的自动注册代码 - 放在同一个命名空间,甚至是相同的代码文件中,以清楚地表明这些代码都是相互依赖的。

As far as I know, that's not really what the assembly scanning functionality is meant for. 据我所知,这不是组装扫描功能的意义所在。 It's more useful when a single assembly has numerous implementations of different interfaces (eg. IRepository<File> , IRepository<Folder> , etc.). 当单个程序集具有许多不同接口的实现时(例如, IRepository<File>IRepository<Folder>等),它会更有用。 So, for example, when you're referencing your test assembly you're injecting test repositories, and when you're in production you're injecting Entity Framework repositories. 因此,例如,当您引用测试程序集时,您将注入测试存储库,而当您正在生产时,您将注入实体框架存储库。

In your case, it doesn't look like any of your examples are fully injecting dependencies. 在您的情况下,它看起来不像您的任何示例完全注入依赖项。 In other words, when you write 换句话说,当你写作

ObjectFactory.GetNamedInstance<ISerializer>("JsonSerializer");

you still have a dependency on the Json serializer by virtue of hard-coding the string, and it wouldn't make sense for StructureMap to ever return some other kind of serializer from that call. 由于对字符串进行了硬编码,你仍然依赖于Json序列化程序,而且从那个调用中返回一些其他类型的序列化程序也没有意义。

I can't tell exactly what you mean to accomplish with StructureMap, but if you need to return a particular serializer depending on a certain set of runtime conditions, you could look into conditional construction . 我无法确切地告诉您使用StructureMap完成什么,但如果您需要根据某些运行时条件返回特定的序列化程序,则可以查看条件构造

On the other hand, it doesn't really sound like a switch of that sort is what you're going for here, so you should definitely consider getting rid of it. 另一方面,它听起来并不像你在这里的那种转换,所以你一定要考虑摆脱它。 After all, the above code is really no different from 毕竟,上面的代码确实没有什么不同

new JsonSerializer();

StructureMap is a wonderful tool, but it's not necessary for every project. StructureMap是一个很棒的工具,但并不是每个项目都需要。

Good luck! 祝好运!

Since your code assumes it is getting a JsonSerializer, create a new IJsonSerializer interface that only the JsonSerializer implements. 由于您的代码假定它正在获取JsonSerializer,因此请创建一个只有JsonSerializer实现的新IJsonSerializer接口。 Any class that needs the JsonSerializer should accept an IJsonSerializer. 任何需要JsonSerializer的类都应该接受IJsonSerializer。 If you still need the ISerializer interface to be common across all serializers, the IJsonSerializer can be used just as a marker interface. 如果仍需要ISerializer接口在所有序列化器中都是通用的,则IJsonSerializer可以用作标记接口。

Alternatively, you can tie the specific ISerializer implementation to your class when you register your class in StructureMap. 或者,当您在StructureMap中注册类时,可以将特定的ISerializer实现绑定到您的类。

x.For<MySomeClass>().Use(c => new MySomeClass(c.GetInstance<JsonSerializer>()));

I am curious. 我好奇。 What value a ISerializer on it's own is adding? ISerializer对它自己有什么价值呢? Let's go from specific implementation to one or many picked at runtime. 让我们从具体实现转到运行时选择的一个或多个。

If your type is dependent on a specific type of serializer take a dependency on it (IJsonSerializer). 如果您的类型依赖于特定类型的序列化程序,则依赖它(IJsonSerializer)。 This requires that a default instance of that type be registered with the container. 这要求在容器中注册该类型的默认实例。

However, if you are thinking more of having ISerializers as Strategies you would register all your ISerializers and then take a dependency on an array of them and StructureMap will push in an array of all registered ISerializers. 但是,如果您更多地考虑将ISerializers作为策略,那么您将注册所有ISerializers,然后依赖它们的数组,StructureMap将推入所有已注册ISerializers的数组。 The class consuming these serializers then is responsible for selecting which one to use. 然后,使用这些序列化器的类负责选择使用哪一个。

In the strategy scenario you'll likely need some metadata on the serializer for use by your coordinating class to discriminate between them. 在策略场景中,您可能需要序列化程序上的一些元数据供协调类用于区分它们。 IMHO, this should really not be container configuration, like names on registered types, but metadata on the implementation itself. 恕我直言,这应该不是容器配置,如注册类型上的名称,但实现本身的元数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM