Appearance
Registration
Lamar provides a streamlined fluent interface called the Registry DSL to configure a Lamar Container with both explicit registrations and conventional auto-registrations based on the older StructureMap syntax. In addition, Lamar also directly supports the ASP.Net Core DI style of service registrations (IServiceCollection) to ease the usage of Lamar with the entire .Net Core ecosystem.
The first step in using Lamar is configuring a Container
object. The following examples are based on the usage of the ServiceRegistry DSL.
Let's say that you have a simple set of services like this:
cs
public interface IBar
{
}
public class Bar : IBar
{
}
public interface IFoo
{
}
public class Foo : IFoo
{
public Foo(IBar bar)
{
Bar = bar;
}
public IBar Bar { get; }
}
A simple configuration of a Lamar Container might then be:
cs
// Example #1 - Create an container instance and directly pass in the configuration.
var container1 = new Container(c =>
{
c.For<IFoo>().Use<Foo>();
c.For<IBar>().Use<Bar>();
});
// Example #2 - Create an container instance but add configuration later.
var container2 = new Container();
container2.Configure(c =>
{
c.For<IFoo>().Use<Foo>();
c.For<IBar>().Use<Bar>();
});
Initializing or configuring the container is usually done at application startup and is located as close as possible to the application's entry point. This place is sometimes referred to as the composition root of the application. In our example we are composing our application's object graph by connecting abstractions to concrete types.
We are using the fluent API For<TInterface>().Use<TConcrete>()
which registers a default instance for a given service type (the TInterface type in this case). In our example we want an new instance of Foo
every time we request the abstraction IFoo
.
The recommended way of using the ServiceRegistry DSL is by defining one or more ServiceRegistry
classes. Typically, you would subclass the ServiceRegistry
class, then use the Fluent API methods exposed by the ServiceRegistry
class to describe a Container
configuration. Here's a sample ServiceRegistry
class used to configure the same types as in our previous example:
cs
public class FooBarRegistry : ServiceRegistry
{
public FooBarRegistry()
{
For<IFoo>().Use<Foo>();
For<IBar>().Use<Bar>();
}
}
When you set up a Container
, you need to simply direct the Container
to use the configuration in that ServiceRegistry
class.
cs
// Example #1
var container1 = new Container(new FooBarRegistry());
// Example #2
var container2 = new Container(c => { c.AddRegistry<FooBarRegistry>(); });
// Example #3 -- create a container for a single Registry
var container3 = Container.For<FooBarRegistry>();
INFO
The Lamar team highly recommends using ServiceRegistry
classes for your real application configuration. The syntax is shorter inside the Registry class constructor than from within the Container
constructor method. In addition, Registry classes can be used to modularize the configuration of a bigger application into more manageable chunks. Lastly, using ServiceRegistry
classes makes it easier to stand up additional Container
objects in testing scenarios that reflect the real application composition.
In real world applications you also have to deal with repetitive similar registrations. Such registrations are tedious, easy to forget and can be a weak spot in your application. Lamar provides auto-registration and conventions which mitigates this pain and eases the maintenance burden. Lamar exposes this feature through the ServiceRegistry DSL by the Scan
method.
In our example there is an reoccurring pattern, we are connecting the service type ISomething
to a concrete type Something
, meaning IFoo
to Foo
and IBar
to Bar
. Wouldn't it be cool if we could write a convention for exactly doing that? Fortunately Lamar has already one build in. Let's see how we can create an container with the same configuration as in the above examples.
cs
// Example #1
var container1 = new Container(c =>
c.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
}));
// Example #2
var container2 = new Container();
container2.Configure(c =>
c.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
}));
We instruct the scanner to scan through the calling assembly with default conventions on. This will find and register the default instance for IFoo
and IBar
which are obviously the concrete types Foo
and Bar
. Now whenever you add an additional interface IMoreFoo
and a class MoreFoo
to your application's code base, it's automatically picked up by the scanner.
Sometimes classes need to be supplied with some primitive value in its constructor. For example the System.Data.SqlClient.SqlConnection
needs to be supplied with the connection string in its constructor. No problem, just set up the value of the constructor argument in the bootstrapping:
cs
var container = new Container(c =>
{
//just for demo purposes, normally you don't want to embed the connection string directly into code.
c.For<IDbConnection>().Use<SqlConnection>().Ctor<string>().Is("YOUR_CONNECTION_STRING");
//a better way would be providing a delegate that retrieves the value from your app config.
});
So far you have seen an couple of ways to work with the ServiceRegistry DSL and configure a Container
object. We have seen examples of configuration that allow us to build objects that don't depend on anything like the Bar
class, or do depend on other types like the Foo
class needs an instance of IBar
. In our last example we have seen configuration for objects that need some primitive types like strings in its constructor function.