Continuation of the
first post about StructureMap
The first part covered the following topics:
- Installation
- Registration (Base, Profiles, Plugins, Crawl, Deployment)
In this part we will talk about:
- Constructors (Simple Types, Default Constructor, Compound Types, Type Casting, Specifying Arguments)
- Properties (Simple setting of properties, Built-in setting of properties, Setting properties by a framework, Finishing existing classes)
- Lifetime
Constructors
A very important question in real programming in relation to resolving dependencies in IoC containers, how to deal with classes that have several constructors. How to initialize them, how to set parameters, how to modify and so on and so forth. I hope that most of the questions below will be answered. StructureMap is really powerful and flexible.
')
Before you begin to describe the capabilities of the framework, you need to talk about test classes. This time they will be more difficult. Inheritance, constructors with simple types, with composite.
So, let us have the following classes:
public interface IClassA : IClass { int A { get; set; } } public interface IClassB : IClass {} public class ClassA : IClassA { public int A { get; set; } public int B { get; set; } public Class1 Class1 { get; set; } [DefaultConstructor] public ClassA() {} public ClassA(int a) { A = a; } } public class ClassB : IClassB { public IClassA ClassA; public ClassB(IClassA classA) { ClassA = classA; } } public class ClassM : IClassA { public int A { get; set; } public ClassM(int a) { A = a; } public ClassM(int a, int b) { A = a + b; } }
They are with some redundancy at the moment, which come in handy a little further in the narration.
So let's start with the simplest options.
Simple types
Let's start with the class ClassA, in which one of the constructors takes an integer parameter.
public class WithSimpeArguments { public IContainer Container; public WithSimpeArguments() { Container = new Container(x => x.For<IClassA>().Use<ClassA>() .Ctor<int>().Is(5) ); } }
As you can see from the example, an indication of the initialization of the constructor by values is added to the already familiar declaration. In this case, the constructor declaration follows the simplified scheme, since there is only one parameter of type int. During the initialization process, we immediately indicated the value. In this case, the StructureMap will prompt that the value should be integer.
To test the work, you can call the already familiar code
private static string WithSimpleArgumentsExample() { var container = new WithSimpeArguments().Container; var classA = (ClassA) container.GetInstance<IClassA>(); return classA.A.ToString(); }
As a result, the number 5 is displayed on the console.
Let's complicate the example for the case when we have two parameters of the same type in the constructor. As an experimental class ClassM.
public class WithMultipleSimpeArguments { public IContainer Container; public WithMultipleSimpeArguments() { Container = new Container(x => x.For<IClassA>().Use<ClassM>() .Ctor<int>("a").Is(6) .Ctor<int>("b").Is(5)); } }
Now the
.Ctor <int> ("a") construction
. Is (6) is supplemented with the name of the argument, so that the framework can match the values to the arguments exactly. The framework always finds the most "greedy" constructor and wants all arguments to be initialized. You cannot omit the setting of the value for the second argument in the current class implementation. But you can specify StructureMap, which constructor to use by default, for this you need to use the
DefaultConstructor attribute.
Default constructor
The
DefaultConstructor attribute allows you to explicitly specify which constructor to use to create an instance of a class. So that in the previous example it was possible to omit the declaration of the variable b and nothing fell during the work.
public class ClassM : IClassA { public int A { get; set; } [DefaultConstructor] public ClassM(int a) { A = a; } public ClassM(int a, int b) { A = a + b; } }
Now you can use the constructor with one parameter.
Composite types
Working with composite types is quite easy, because StructureMap itself detects and resolves all dependencies by composite types. Those. if you look at the beginning, at the classes, you can see that the class ClassB is initialized by the class ClassA. Now we will see that nothing special is needed to resolve such dependencies.
public class WithObjectArguments { public IContainer Container; public WithObjectArguments() { Container = new Container(x => { x.For<IClassA>().Use<ClassA>().Ctor<int>().Is(5); x.For<IClassB>().Use<ClassB>(); }); } }
As you can see from the example, no additional operators are applied. But you can request the class ClassB, which will be initialized by ClassA.
private static string WithObjectArgumentsExample() { var container = new WithObjectArguments().Container; var classA = (ClassB) container.GetInstance<IClassB>(); return classA.ClassA.A.ToString(); }
The figure 5 will be displayed on the screen.
Cast
If you look at the classB declaration of a class, you can see that the variable in the constructor is an interface, not a specific type. Rewrite the class so that instead of the interface, the constructor accepts the class ClassA
public class ClassB : IClassB { public ClassB(ClassA classA) { ClassA = classA; } }
Now type binding will not occur because the container registers and returns one data type. In our case, this is IClassA.
private static string WithObjectArgumentsForwardingAndWiringExample() { var container = new WithObjectArgumentsForwarding().Container; var classB = (ClassB) container.GetInstance<IClassB>(); return classB.ClassA.A.ToString(); }
This code will return 0, because the default constructor for the class ClassA will be called, no binding has occurred.
As you guessed it is not a problem. You can specify a StructureMap as to what can be reduced. This is done with the help of the
Forward command which needs to specify the source type and the desired one.
public class WithObjectArgumentsForwarding { public IContainer Container; public WithObjectArgumentsForwarding() { Container = new Container(x => { x.For<IClassA>().Use<ClassA>().Ctor<int>().Is(5); x.Forward<IClassA, ClassA>(); x.For<IClassB>().Use<ClassB>(); }); } }
Now you can call the WithObjectArgumentsForwardingAndWiringExample method and get 5 in the response.
Setting arguments
Most often, it is not possible to know in advance and set the arguments for the class, they become known only at the time of creating the required class. And since these are working days of writing programs, support for setting arguments at the time of class creation could not appear in StructureMap.
The process of invoking a class with new argument values is similar to communicating in the Yoda master language, but that is the specifics of DSL. Technical limitations I mean.
So let us want to call the class ClassB with the new class ClassA. To do this, you need the With operator.
private static string WithObjectArgumentsOverridingExample() { var container = new WithObjectArgumentsForwarding().Container; var classB = (ClassB) container .With(new ClassA(8)) .GetInstance<IClassB>(); return classB.ClassA.A.ToString(); }
Since the framework, in this case, can uniquely determine which argument to associate with an instance of the class, it is not necessary to specify the name of the argument.
If you need to specify a larger number of arguments for the class, then you need to use a larger number of the With method with the indication of the parameter name and its value.
private static string WithObjectArgumentsOverridingExample() { var container = new WithObjectArgumentsForwarding().Container; var classB = (ClassB) container .With(new ClassA(8)) .With("s").EqualTo(5) .GetInstance<IClassB>(); return classB.ClassA.A.ToString(); }
Now, working with designers, I think, is covered sufficiently so that you can solve almost all the architectural approaches that are in your application.
Properties
In addition to the required constructor parameters, it is very pleasant to set the class property values when creating an instance of a class. Those. instead of writing
var classA = new ClassA(); classA.A = 8; classA.B = 20;
You can set properties like:
var classA = new ClassA { A = 8, B = 20 };
For StuctureMap it is quite capable, all in the same elegant and understandable way.
Simple setting of properties
In the simplest case, as in the illustration above, you can set parameters using the
SetProperty method.
public class WithArgumentsSetProperty { public IContainer Container; public WithArgumentsSetProperty() { Container = new Container(x => x.For<IClassA>().Use<ClassA>() .Ctor<int>().Is(5) .SetProperty(p => { pA = 8; pB = 20; })); } }
As you can see from the example, the properties are strongly typed and you can use intelliSense hints, i.e. the task of properties proceeds easily and naturally. It is clear that you can not set all the properties, but only those that you want to initialize at the stage of building an instance of a class.
Built-in setting of properties
To use the inline parameter setting, use the
Setter method. Using this method, you can set values for one parameter at a time. Since the argument of the method is a function.
The simplest is to explicitly set the parameter for initialization.
public class WithArgumentsSetterExplicit { public IContainer Container; public WithArgumentsSetterExplicit() { Container = new Container(x => { x.For<IClass1>().Use<Class1>(); x.For<IClassA>().Use<ClassA>() .Ctor<int>().Is(5) .Setter(c => c.Class1).Is(new Class1()); }); } }
The example shows that the Class1 property will always be initialized by the new class Class1. This entry should be used if the class has more than one property of the same type. If you have only one property of a given type, then the framework will be able to independently determine which property to assign the value of the passed parameter.
So, implicit property initialization.
public class WithArgumentsSetterImplicit { public IContainer Container; public WithArgumentsSetterImplicit() { Container = new Container(x => { x.For<IClass1>().Use<Class1>(); x.For<IClassA>().Use<ClassA>() .Ctor<int>().Is(5) .Setter<Class1>().Is(new Class1()); }); } }
In this example, we did not specify the name of the property, but everything went well, because the property of type Class1 is only one.
Setting properties by framework
Since StructureMap can automatically substitute instances of classes into constructor arguments, can it automatically populate the properties of the classes?
Of course he can!
But of course he will do it not according to his own will and not for all the fields, but only for those that will be indicated as auto-complete.
You can modify the previous example, so that property dependencies are resolved by StructureMap and controlled by it.
public class WithArgumentsSetterImplicitDefault { public IContainer Container; public WithArgumentsSetterImplicitDefault() { Container = new Container(x => { x.For<IClass1>().Use<Class1>(); x.For<IClassA>().Use<ClassA>() .Ctor<int>().Is(5) .Setter<Class1>().IsTheDefault(); }); } }
In the example, the new method
IsTheDefault has appeared , which tells the framework to resolve the dependency with its own means. Those. in this case, a property of type Class1 for classA will be created and assigned based on how Class1 is registered.
There is also a batch initialization of parameters, when you can say that all properties of a particular type must be initialized with default values. To do this, use the
SetAllProperties command.
public class WithArgumentsSetterBatchImplicitDefault { public IContainer Container; public WithArgumentsSetterBatchImplicitDefault() { Container = new Container(x => { x.For<IClass1>().Use<Class1>(); x.For<IClassA>().Use<ClassA>() .Ctor<int>().Is(5); x.SetAllProperties(c => c.OfType<Class1>()); }); } }
Using this directive, StructureMap automatically initializes all properties of the called classes, for which the property type is Class1.
Completion of existing classes
Sometimes it happens that you can only get a ready-made class, without the ability to somehow influence its creation. At the same time, I want to automate the filling of a heap of class fields in automatic mode. And this is possible with the help of StructureMap.
Let ClassA come to us which is not created in our system. It is necessary to initialize its property of type Class1. First we set up StructureMap.
public class WithArgumentsBuildUp { public IContainer Container; public WithArgumentsBuildUp() { Container = new Container(x => { x.For<IClass1>().Use<Class1>(); x.Forward<IClass1, Class1>(); x.SetAllProperties(c => c.OfType<Class1>()); }); } }
Now you can call the
BuildUp method which will complete the object according to the available configurations.
var container = new WithArgumentsBuildUp().Container; var classA = new ClassA(14); container.BuildUp(classA);
On the second line, the property Class1 = null, after calling BuildUp, the object is completely ready.
Lifetime
An important factor is the ability to control the lifetime of the object. For some classes it is necessary to receive the same copy, for others - each time new ones. This can also be managed during the process of creating rules in a container.
The framework operates seven policies for managing the lifetime of an object:
- Per reques (default) - a new object is created every time.
- HttpContextLifecycle
- Singletonlifecycle
- ThreadlocalStorageLifecycle
- UniquePerRequestLifecycle - ensures that the entire chain of objects initializing the requested object will be unique.
- HttpSessionLifecycle
- HybridLifecycle is HttpSessionLifecycle or ThreadlocalStorageLifecycle
- HybridSessionLifecycle - or HttpContextLifecycle, or HttpSessionLifecycle
Consider some of them on the example of a simple class
public class ClassX : IClassX { public int Counter { get; private set; } public void Increase() { Counter++; } } public interface IClassX {}
First in line will be let
Singleton .
public class LifecycleSingleton { public IContainer Container; public LifecycleSingleton() { Container = new Container(x => x.For<IClassX>().LifecycleIs(new SingletonLifecycle()).Use<ClassX>()); Container = new Container(x => x.For<IClassX>().Singleton().Use<ClassX>()); } }
Abbreviated methods are defined for the basic life policies. Those. You can use both
Singleton () and
LifecycleIs (new SingletonLifecycle ()) .
As a test, you can use a visual example:
private static string LifecycleSingleton() { var singleton = new LifecycleSingleton().Container; var classX = (ClassX) singleton.GetInstance<IClassX>(); classX.Increase(); Console.WriteLine(classX.Counter); classX = (ClassX) singleton.GetInstance<IClassX>(); classX.Increase(); Console.WriteLine(classX.Counter); return "done"; }
As a result, the data will be output to the console: “1, 2, Done”. With a simple ad, we would get: "1, 1, Done."
To store an instance of a class within one thread,
ThreadLocalStorageLifecycle is used, or the abbreviated
HybridHttpOrThreadLocalScoped entry public class LifecycleThreadLocal { public IContainer Container; public LifecycleThreadLocal() { Container = new Container(x => x.For<IClassX>() .LifecycleIs(new ThreadLocalStorageLifecycle()) .Use<ClassX>()); Container = new Container(x => x.For<IClassX>() .HybridHttpOrThreadLocalScoped() .Use<ClassX>()); } }
HttpContextLifecycle has an abbreviated
HttpContextScoped entry
public class LifecycleHttpContext { public IContainer Container; public LifecycleHttpContext() { Container = new Container(x => x.For<IClassX>() .LifecycleIs(new HttpContextLifecycle()) .Use<ClassX>()); Container = new Container(x => x.For<IClassX>() .HttpContextScoped() .Use<ClassX>()); } }
Continued in the next part.