📜 ⬆️ ⬇️

StructureMap - quick reference for work (1/3)

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

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

Link one , two and three . Everything else StructureMap is also quite fast.
Here are some very interesting collections of materials comparing the http://www.sturmnet.org/blog/2010/03/04/poll-results-ioc-containers-for-net frameworks
')
I think you can omit the argument about whether to use such containers in your project or not, and whether to write 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 this was enough at that time. With the development of the project, somehow there was no time to transfer everything to some other technology. Then there was Unity, Unity, but in the end I came to the conclusion that I should try StructureMap and did not regret it even once.

What are, in my opinion, the advantages of StructureMap:

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

A brief outline of the subsequent material is as follows:


Installing StructureMap


I recommend installing StructureMap in my application using NuGet. A single command in the Package Manager Console (install-Package StructureMap) or search and install using a wizard is the easiest way to get. But if you want, you can download the project from the official page http://github.com/structuremap/structuremap/downloads


check in


The most important action probably for any IoC container is registration. The extent to which it is convenient and clear depends on whether people will use it and how likely the tool will be misused.

The author advises to use DSL as widely as possible and resort to the configuration file only when you need to separately specify connection strings, URL addresses, file paths and everything else in the same spirit.

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 for the time being the main one for demonstrating the possibilities. But you do not worry, further classes and connections will be more difficult, because in such classes you will not show much either.


The foundation


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

Registration using the static ObjectFactory class.
 public class RegisterByObjectFactory { public RegisterByObjectFactory() { ObjectFactory.Initialize(x => { x.For<IClass1>().Use<Class1>(); x.For<IClass2>().Use<Class2>(); }); } } 

Everything is done using DSL and lambda expressions. DSL itself is quite concise and clear, the resulting code easily folds up into meaningful expressions. In this case, you can easily read that you must use Class1 for the IClass1 interface .

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

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

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

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

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

The next object in turn is the Registry . Actually, you indirectly called him 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<Class1>(); registry.ForConcreteType<Class2>(); 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 sub-container, assemble it and then transfer to the assembly in one container. In this case, the addition of the Registry at the time of its creation is illustrated, but nothing prevents to write:
 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<Class1>(); var class2Inst = container.GetInstance<Class2>(); 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<IClass>().Use<Class1>()); registry.Profile("p2", x => x.For<IClass>().Use<Class2>()); Container = new Container(x => x.AddRegistry(registry)); } } 

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

The profile name may only be a string variable, but this is no longer a big problem. I mean, you don’t really have to write a profile name as in the example with an open string. Harmful to karma.

After installing the active profile, you can work with the container as usual. As a result, when the same line is executed, container.GetInstance <IClass> (); we get different classes.


Plugins


There is another way to solve the problem of getting a particular class on a common interface, this is a named plugin.

A little about the terminology. In IntelliSense and a little here you can come across the term plugin, PluginType and PluggedType, which generally means the type you want to get . Those. In all previous examples of IClass, PluginType can be called, and Class1 or Class2 - PluggedType.
 public class RegisterAsPlugin { public IContainer Container; public RegisterAsPlugin() { Container = new Container(x => { x.For<IClass>().Use<Class1>().Named("Class1"); x.For<IClass>().Use<Class2>().Named("Class2"); }); } } 

The example shows that we register classes by a common interface, but at the same time we give them specific names. With the Named method, you can now easily request a specific type.
 private static string PluginExample() { var container = new RegisterAsPlugin().Container; var class1Inst = container.GetInstance<IClass>("Class1"); var class2Inst = container.GetInstance<IClass>("Class2"); var instanceDef = container.GetInstance<IClass>(); return container.WhatDoIHave(); } 

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

The default behavior follows the adage “who is the last, he and the father”, i.e. in this case, an instance of the class Class2 will fall into the instanceDef variable. However, we can define quite explicitly the “default” class. To do this, use a slightly different form of registration plugins.
 public class RegisterAsPluginWithDefault { public IContainer Container; public RegisterAsPluginWithDefault() { Container = new Container(x => x.For<IClass>() .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 literally: for the IClass interface, add implementations of the Class1 types Class1 , Class2 named Class2, use Class1 (in this case, a very specific class, but you could write .Use <Class1 in the previous examples) > ()).

In this case, the Use method says what type will be used for the default interface. If now run the following code
 var instanceDef = container.GetInstance<IClass>(); 

then we get an instance of class Class1.

Use already in itself exposes the type used by default.


Scanning


Logical continuation will be the search and automatic registration of types in the container. Imagine that not two classes are inherited from the general interface, but 50! It will be very sad and boring to fill in your hands with all these registrations and dependencies. In such a case, StructureMap has a Scan method that runs through the assemblies or folders of interest and registers suitable objects. In this way, you can implement the plugin structure for the application and even compete with the MEF in something or replace it.

In order for the Scan method to find and register types, several conditions must be met:

The scan method and behavior can be overridden, but so far this will not be considered.

Assembly specification for scanning can be defined in several ways:

After you have specified the experimental assemblies, you can customize the import process in more detail using the methods for including / excluding types according to 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, which should be guided by default conventions. And the last line is the command to start the search. Previously, everything worked without explicit instructions. But now we 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<IClass>(); return container.WhatDoIHave(); } 

Notice that the Get All Instances method is being called. If you call a method to get a particular class from the registered ones, then 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(); })); } } 

To the AddAllTypesOf method they added a clarifying rule that all classes should be registered by their name. After this modification, you can work with specific types:
 var instance = container.GetInstance<IClass>("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<IClass>("Class1"); var instance2 = container.GetInstance<IClass>("Class2"); var class1Inst = container.GetInstance<IClass>(); container.Inject(typeof (IClass), new Class2()); var class2Inst = container.GetInstance<IClass>(); 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 only need to specify the type of the plug-in 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 look at how to deal with classes that have constructors with parameters.

To be continued.

Source: https://habr.com/ru/post/125613/


All Articles