StructureMap - a quick reference for work (1/3)

    Today I want to talk about the StructureMap IoC container (and this is not a translation of the outdated official documentation), which I liked much more than Unity. Although, frankly, my relationship with Unity did not work out from the very beginning, when I saw kilometer-long configuration files for it or two hundred icon configuration lines in the code. Let's not talk about sad things.

    StructureMap not only seemed to me more convenient than other DI \ IoC implementations , just type in StructureMap vs Unity in Google and get a bunch of links where people discuss and demonstrate clearly that StructureMap is the most convenient, flexible and natural in work.

    Link one , two and three . In addition, StructureMap is also quite fast.
    Here are some very interesting collections of materials comparing frameworks http://www.sturmnet.org/blog/2010/03/04/poll-results-ioc-containers-for-net

    I think you can omit the discussion about whether such containers are worth it to use in your project or not, and is it worth writing your own implementation. I can say that in my practice at first it happened that there were self-written implementations that were noticeably inferior in terms of capabilities and convenience, but they solved their specific tasks and that was enough at that time. With the development of the project, there was somehow a lack of time to transfer everything to some other technology. Then there was Unity, Unity, but in the end I came to the conclusion that I had to try StructureMap and did not regret it even once.

    What are, in my opinion, the advantages of StructureMap:
    • DSL setup
    • Very flexible customization of everything
    • Easy End Use
    • Opportunities for checking internal relationships
    • Out-of-the-box testing support

    There are still many interesting and useful things in StructureMap, but it would be wrong to attribute them to the pluses, it is better to call them a pleasant addition that only make life easier.

    A brief outline of the follow-up material is as follows:
    • Installation
    • Registration (Basis, Profiles, Plugins, Scanning, Deployment)
    • Constructors (Simple types, Default constructor, Compound types, Coercion, Setting arguments)
    • Properties (Simple property setting, Built-in property setting, Setting properties with a framework, Extending existing classes)
    • Lifetime
    • Interceptors (OnCreation, EnrichWith)
    • Generic types
    • Attributes (DefaultConstructor, ValidationMethod, All Others)
    • Tests


    Install StructureMap


    I recommend using NuGet to install StructureMap in your application. One command in the Package Manager Console (install-Package StructureMap) or searching and installing with the wizard are the easiest ways to get it. But if you want, you can download the project from the official page http://github.com/structuremap/structuremap/downloads


    registration


    Probably the most important action for any IoC container is registration. It depends on how convenient and understandable it is, whether people will use it and how likely improper use of the tool is.

    The author advises using DSL as widely as possible and resorting to the configuration file only when you need to separately set connection strings, URLs, file paths and everything else in the same vein.

    Let's start with the simplest, registering a class with a default constructor.

    Suppose we have such a simple set of classes:
    public interface IClass {}
    public interface IClass1 : IClass {}
    public interface IClass2 : IClass {}
    public class Class1 : IClass1 {}
    public class Class2 : IClass2 {}

    This set will be so far the main one to demonstrate the possibilities. But do not worry, further classes and connections will be more difficult, because you will not show much at such classes either.


    The foundation


    Registration is possible using the Container , using the static ObjectFactory class , or using the Registry class . Gradually consider registration using all of these objects. The main class for registration is the Registry, the rest of the classes skip its functionality, for convenience.

    Register with the static ObjectFactory class.
    public class RegisterByObjectFactory {
        public RegisterByObjectFactory() {
             ObjectFactory.Initialize(x => {
                       x.For().Use();
                       x.For().Use();
             });
       }
    }

    Everything is done using DSL and lambda expressions. The DSL itself is quite concise and understandable, the resulting code easily folds into meaningful expressions. In this case, you can easily read that interface IClass1 need to use Class1 .

    Obtaining objects can be done in the following way, also intuitive:
    private static string ObjectFactoryExample() {
         new RegisterByObjectFactory();
        var class1Inst = ObjectFactory.GetInstance();
        var class2Inst = ObjectFactory.GetInstance();
        return ObjectFactory.WhatDoIHave();
    }

    The main method to get the GetInstance object, in this case, with the interface. Further we will consider various ways of getting ready-made objects. You may notice that the method returns the string that we get from the method with the speaking name WhatDoIHave . Using this method, you can diagnose the interior of the container, see what, how and where it is registered.

    For a long time, the author of the framework could not accept the term container, in relation to his offspring, so the next method was hidden for a long time and only in later implementations opened the natural course of registration, as it was implemented inside, behind a static class. So,
    public class RegisterByContainer {
        public IContainer Container;
        public RegisterByContainer() {
            Container = new Container(x => {
                                        x.For().Use();
                                        x.For().Use();
                                                });
       }
    }

    At first glance, everything is the same, the lambda is the same, but now we are creating a class that we will then give to the outside, and by which we will access the container. Those. once again, ObjectFactory is just a static wrapper class over the Container class.

    Getting objects will follow the same scenario:
    private static string ContainerExample() {
        var container = new RegisterByContainer().Container;
        var class1Inst = container.GetInstance();
        var class2Inst = container.GetInstance();
        return container.WhatDoIHave();
    }

    The next object in line is the Registry . Actually, you indirectly called it all the previous times. For a change we will register specific classes.
    public class RegisterByRegister {
        public IContainer Container;
        public RegisterByRegister() {
            var registry = new Registry();
            registry.ForConcreteType();
            registry.ForConcreteType();
            Container = new Container(x => x.AddRegistry(registry));
       }
    }

    In this case, the ForConcreteType method is used , which is a synonym for . For < T> (). Use < T> () . You can also see that the Registry class can be used as a subcontainer, assemble it and then transfer it to the assembly in one container. In this case, the addition of the Registry at the time of creation is illustrated, but nothing will stop you from writing:
    Container = new Container();
    Container.Configure(x => x.AddRegistry(registry));

    Reading "concrete" classes is no different from the usual:
    private static string ConcreteClassExample() {
        var container = new RegisterByRegister().Container;
        var class1Inst = container.GetInstance();
        var class2Inst = container.GetInstance();
        return container.WhatDoIHave();
    }


    Profiles


    StructureMap allows you to group class mappings using named profiles. Those. You can quickly switch between mapping classes.
    public class RegisteringProfiles {
        public IContainer Container;
        public RegisteringProfiles() {
            var registry = new Registry();
            registry.Profile("p1", x => x.For().Use());
            registry.Profile("p2", x => x.For().Use());
            Container = new Container(x => x.AddRegistry(registry));
       }
    }

    Here it is worth paying attention that the classes Class1 and Class2 are registered by the common interface, but in different profiles. In order to get the required class, you need to switch between profiles in the container using the SetDefaultProfile method which takes the profile name.
    private static string ProfilesExample() {
        var container = new RegisteringProfiles().Container;
        container.SetDefaultsToProfile("p1");
        var class1Inst = container.GetInstance();
        container.SetDefaultsToProfile("p2");
        var class2Inst = container.GetInstance();
       return container.WhatDoIHave();
    }

    The profile name can only be a string variable, but this is no longer a big problem. I mean, in reality, you should not write the profile name as in the example with an open line. Harmful to karma.

    After setting the active profile, you can work with the container as usual. As a result, when executing the same line container.GetInstance(); we get different classes.


    Plugins


    There is another way to solve the problem of obtaining a specific class by a common interface, this is a named plugin.

    A little bit about terminology. In IntelliSense and a little here you can find the term plugin, PluginType and PluggedType, in general, this means the type that you want to get . Those. in all previous examples, IClass can be called PluginType, and Class1 or Class2 can be called PluggedType.
    public class RegisterAsPlugin {
         public IContainer Container;
         public RegisterAsPlugin() {
             Container = new Container(x => {
                                         x.For().Use().Named("Class1");
                                         x.For().Use().Named("Class2");
                                                 });
        }
    }

    As an example, we can see that we register classes by a common interface, but at the same time give them specific names. Using the Named method, you can now easily query for a specific type.
    private static string PluginExample() {
         var container = new RegisterAsPlugin().Container;
         var class1Inst = container.GetInstance("Class1");
         var class2Inst = container.GetInstance("Class2");
          var instanceDef = container.GetInstance();
          return container.WhatDoIHave();
    }

    The example shows how you can access the container and get a specific type on a common interface. However, here we will raise the question at the same time, what will happen if we try to call the GetInstance method with a common interface without specifying a plugin name?

    The default behavior follows the saying "who is last, that is dad", i.e. in this case, an instance of the Class2 class will get into the instanceDef variable . However, we can explicitly define the default class. To do this, use a slightly different form of registering plugins.
    public class RegisterAsPluginWithDefault {
        public IContainer Container;
        public RegisterAsPluginWithDefault() {
            Container = new Container(x => x.For()
                                               .AddInstances(i => {
                                                   i.Type(typeof (Class1)).Named("Class1");
                                                   i.Type(typeof (Class2)).Named("Class2");
                                               })
                                              .Use(new Class1())
            );
        }
    }

    And again we can say that the example describes itself. If you read it, it will literally be literal: for the IClass interface, add implementations of the types Class1 named Class1, Class2 named Class2, use Class1 (in this case, a very specific class, but you could also write .Use in the previous examples()).

    In this case, it is the Use method that tells which type will be used for the default interface. If you now run the following code
    var instanceDef = container.GetInstance();

    then we get an instance of class Class1.

    Use already exposes the default type.


    Scanning


    The logical continuation will be the search and automatic registration of types in the container. Imagine that we have not two classes inherited from a common interface, but 50! It will be very sad and boring to fill all these registrations and dependencies with your hands. For such a case, StructureMap has a Scan method that runs through the assemblies or folders you are interested in and registers suitable objects. Thus, it is possible to implement the structure of plugins for the application and even compete with MEF in some way or replace it.

    In order for the Scan method to find and register types, several conditions must be met:
    • The type must be explicit; generic types are not automatically registered;
    • The type must have a public constructor;
    • A constructor cannot have arguments of primitive types;
    • Multiple registrations are not allowed.

    The scanning method and behavior may be overridden, but for now this will not be considered.

    The assembly specification for scanning can be specified in several ways:
    • Explicitly spell the assembly name or pass it on;
    • Access the calling assembly;
    • Find an assembly containing a specific type;
    • Find builds on a specific path.

    After you pointed out the experimental assemblies, you can configure the import process in more detail using the methods of including / excluding types by various parameters. For more detailed information, it is better to refer to the documentation . It is outdated, but gives a general idea of ​​the possibilities.

    So, let's take a simpler example:
    public class RegisterByScan {
         public IContainer Container;
         public RegisterByScan() {
             Container = new Container(x => x.Scan(s => {
                                                 s.AddAllTypesOf(typeof (IClass));
                                                 s.AssembliesFromPath(".");
                                                 s.WithDefaultConventions();
                                                 s.LookForRegistries();
                                                 }));
        }
    }

    In this class, we say that we want to import all types that implement the IClass interface from the application folder, we should be guided by default conventions. And the last line is the command to start the search. Previously, everything worked without an explicit indication. But now you need to clearly define the LookForRegistries method .

    After the method works, you can see what was found and registered in the container.
    private static string RegisterByScanExample() {
        var container = new RegisterByScan().Container;
        var instances = container.GetAllInstances();
        return container.WhatDoIHave();
    }

    Note that the Get All Instances method is now being called . If you call a method to get a specific class from the registered ones, there will be an error, since StructureMap does not know which class to return "by default".

    Honestly, with such an implementation it is impossible to use the results of the Scan command. In order for everything to become good, and it would be possible to refer to the found classes by name, you need to rewrite the scan code a bit.
    public class RegisterByScanWithNaming {
         public IContainer Container;
         public RegisterByScanWithNaming() {
             Container = new Container(x => x.Scan(s => {
                                                 s.AddAllTypesOf(typeof (IClass)).NameBy(t => t.Name);
                                                 s.AssembliesFromPath(".");
                                                 s.WithDefaultConventions();
                                                 s.LookForRegistries();
                                                }));
        }
    }

    A clarifying rule has been added to the AddAllTypesOf method that all classes must be registered by their name. After this modification, you can work with specific types:
    var instance = container.GetInstance("Class1");


    Implementation


    In the process of working with the container, you can override the type returned by default. This is mainly used in tests. Demonstration of work:
    private static string InjectExample() {
         var container = new RegisterAsPluginWithDefault().Container;
         var instance1 = container.GetInstance("Class1");
         var instance2 = container.GetInstance("Class2");
         var class1Inst = container.GetInstance();
         container.Inject(typeof (IClass), new Class2());
         var class2Inst = container.GetInstance();
         return container.WhatDoIHave();
    }

    We previously declared the RegisterAsPluginWithDefault class, which returns the default Class1 class. Using the Inject method, you can override the return type, you just need to specify the type of the plugin and the new class.

    These examples were on the general principles of registration, when the classes themselves are simple. In the next topic, we will discuss how to deal with classes that have constructors with parameters.

    To be continued.

    Also popular now: