Lamar (https://jasperfx.github.io/lamar) is an Inversion of Control (IoC) container for .NET and .NET Core applications. Lamar was built specifically to replace the venerable StructureMap IoC container (https://github.com/structuremap/structuremap). The project goals for Lamar were to:
- Allow easy replacement for existing StructureMap users.
- Be much more performant than StructureMap. StructureMap was optimized for runtime flexibility at the price of performance. Lamar takes the opposite approach and favors performance optimization over runtime flexibility. Depending on use case, Lamar is generally an order of magnitude faster than StructureMap and up to two orders of magnitude faster in some common use cases.
- Provide easy integration with the then new ASP.NET Core service registration abstractions. Lamar natively supports the IoC abstractions in the
Microsoft.Extensions.DependencyInjection
namespace. - Comply with the behavioral assumptions for IoC containers within ASP.NET Core. This caused some significant changes in behavior from StructureMap to Lamar, but also simplified Lamar's internals and, arguably, its usage in comparison to StructureMap.
- Improve the supportability of Lamar over StructureMap by dropping many of the most troublesome advanced features of StructureMap.
In this article, I'll be demonstrating the basics of integrating Lamar in .NET applications using the generic HostBuilder
. Beyond that, I'll show some of the advanced features of Lamar, like diagnostics and conventional registrations that go well beyond the basic functionality in the built-in ServiceProvider in .NET Core. Finally, I'll present some of my recommendations for successfully using IoC tools based on supporting both the StructureMap and Lamar user communities since the early 2000s.
Inversion of Control and Dependency Injection
Before I go any farther, I'd like to do a level set on what the terms Inversion of Control (IoC) and Dependency Injection (DI) mean, and why you should care, regardless of whether or not I can convince you to use Lamar in this article.
First, Inversion of Control means to invert the control flow from traditional, top-down code. Earlier in my career, I worked with an existing system that calculated the potential costs or expected profit from proposed energy trades. The system had been a successful product for my client, but at the time, it was unfortunately limited by its top-down code structure. To illustrate what I mean, consider the entry point for the energy trade calculations shown below:
public class OriginalEnergyTradeCalculator
{
public void CalculateProfitAndCosts()
{
/*
1. Look up outstanding trades in
the database
2. Look up currency exchange rates
from a 3rd party web service
3. Look up current market data from
a second database
4. Look up on hand inventory
from a third database
5. Calculate the expected cost and
profit for each trade
6. Write the costs and profit back
to the original trade database
*/
}
}
The code that performed the business calculations also fetched the necessary data inline from various databases and external services. The result of that structure was that the business logic was intrinsically coupled to the databases and external systems. That was potentially harmful in a couple ways:
- It made the business logic harder to test because of the necessary work to first set up test scenarios in the external systems and databases.
- In this case, the very valuable and proprietary business logic could not really be reused outside the context of the external systems and related databases - and this was a problem because my client at that time wished to harness that logic in completely new contexts and even allow analysts to feed “what if” scenarios into the business logic via Excel.
To alleviate those two pain points, let's finally introduce Inversion of Control into the energy trading system by inverting the control into the proprietary business logic. Instead of the business logic code also being responsible for fetching all its inputs and writing all its outputs to a database, let's make it passive and instead pass in every single thing that code needs, like this conceptual entry point:
public class EnergyTradeCalculator
{
public void CalculateProfitAndCosts(
TradeCollection trades,
MarketData marketData,
OnHandInventory inventory,
CurrencyExchangeRates currencyExchangeRates)
{
// using all these inputs,
// calculate the expected cost and profit
// of making all of the proposed trades
// in the TradeCollection
// argument
}
}
The EnergyTradeCalculator
up above should have all the necessary smarts to calculate profit and costs for proposed energy trades, but it's completely decoupled and ignorant about how any of those inputs get there or where the outputs go downstream. Let's focus on the business logic in isolation to solve one problem at a time. I'll have to build some coordination code around the EnergyTradeCalculator
later of course, but at least I'm now able to reuse the business logic in other contexts and, maybe more importantly, I can much more easily write and execute test cases against that business logic without having to fight the external system state and various databases to set up test inputs.
Dependency Injection is a specific form of Inversion of Control where dependencies of code are passed in from the outside instead of having to be built or instantiated by that code. In the case of the energy trade calculator, sooner or later, there's going to be some code that pulls market data out of a database, and that invariably means needing a database connection string. Using a strong-typed configuration strategy, you might have the MarketDataSource
service shown in Listing 1.
Listing 1: MarketDataSource
public class MarketDataOptions
{
public string ConnectionString { get; set; }
}
public class MarketDataSource : IMarketDataSource
{
private readonly MarketDataOptions _options;
public MarketDataSource(MarketDataOptions options)
{
_options = options;
}
public Task<MarketData> FindCurrentMarketData()
{
// using the connection string at
// _options.ConnectionString,
// connect to a database and query
// for all the necessary
// market data
return Task.FromResult(new MarketData());
}
}
You really don't want MarketDataSource
to have to “know” how to find its own configuration data for the market database, so I used Dependency Injection to just slip the configuration data as IOptions<MarketDataSource>
to the MarketDataSource
. At runtime, that connection string information might be coming from configuration files, environment variables, or something like Azure Vault, but MarketDataSource
doesn't have to care because it only depends on the options passed into its constructor function. Regardless of the usage of an IoC container like Lamar, it's frequently valuable just to decouple functional code from the details of how the configuration is stored and accessed for more flexibility or even just easier testing later.
After all that, I'm ready to introduce Lamar as an IoC container to do all that Dependency Injection grunt work for me. In the code below, I'm setting up a minimal Lamar container with basic system configuration and the necessary dependencies to build up the MarketDataSource
service I wrote up above.
var container = new Container(x =>{
x.ForSingletonOf<IConfiguration>().Use(s =>
{
return new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
});
x.ForSingletonOf<MarketDataOptions>()
.Use(s =>
{
return s.GetInstance<IConfiguration>().Get<MarketDataOptions>();
});
x.For<IMarketDataSource>().Use<MarketDataSource>();
});
var source = container.GetInstance<IMarketDataSource>();
Building a Web Service with Lamar
Now let's jump to using Lamar inside a new .NET application. The steps are similar for any kind of .NET technology that uses the generic HostBuilder
for bootstrapping, but I'm going to use a simple Web API for this example. Start a new project from the command line with:
dotnet new webapi
Next, you need to add the Lamar.Microsoft.DependencyInjection
NuGet that's a very slim adapter in between HostBuilder
and the core Lamar NuGet package:
dotnet add package Lamar.Microsoft.DependencyInjection
Using this project template, the new service is primarily configured through a Startup
class with some bootstrapping using the generic HostBuilder
in the Program
class. To use Lamar with the new Web service, add one new line of code to the Program.CreateHostBuilder()
method, as shown below:
public static IHostBuilder CreateHostBuilder
(string[] args) =>
Host.CreateDefaultBuilder(args)
// Make Lamar the DI container
.UseLamar()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
That's honestly enough to use Lamar in place of the built in ServiceProvider in your application. The next step is to enable the new Web service to be able to use native Lamar service registration capabilities. To do that, you're going into the Startup
class, and change the signature of the generated ConfigureServices(IServiceCollection)
method to ConfigureContainer(ServiceRegistry)
as shown below:
public void ConfigureContainer(ServiceRegistry services)
{
// Additional service registrations
services.For<IMarketDataSource>().Use<MarketDataSource>();
services.AddControllers();
}
That's enough, for now, to be able to start up the application. However, one of Lamar's big advantages over the default ServiceProvider is Lamar's very strong support for application diagnostics. To use the most basic diagnostics, I'm going to add one more controller with endpoints to render some of Lamar's built-in container diagnostics for service registrations and type scanning errors in the code below:
public class WhatDoIHaveController : ControllerBase
{
[HttpGet("/_lamar/services")]
public string Services ([FromServices] IContainer container)
{
return container.WhatDoIHave();
}
[HttpGet("/_lamar/scanning")]
public string Scanning ([FromServices] IContainer container)
{
return container.WhatDidIScan();
}
}
If you start up the application and open your browser to the /_lamar/services
URL, you'll see an admittedly ugly textual table listing out all the service registrations in your system. This diagnostic data is often useful to troubleshoot issues with Lamar behavior at runtime, especially when you use conventional registrations or try to override service registration defaults or need specific ordering of service registrations.
To demonstrate a few more Lamar diagnostic capabilities, I'm going to add another package to the new Web API:
dotnet add package Lamar.Diagnostics
This is going to enable extra command line diagnostics through the Oakton (https://jasperfx.github.io/oakton/) library. I'll make a few more changes to the Program.Main()
method to integrate the new command line tooling:
// Main() has to be async while using Oakton,
// and you want to return a status code
// to communicate failures from diagnostic
// commands
public static Task<int> Main(string[] args)
{
return CreateHostBuilder(args)
// Use Oakton as the command line parser
// and command runner
.RunOaktonCommands(args);
}
Now that this is in place, you can check out the static configuration of the application's service registrations with a much nicer visualization:
dotnet run -- lamar-services
One of the problems that folks hit with IoC tool usage is missing service registrations, and often those problems only come out during runtime. To blunt this issue, Lamar containers can do a test run to check that all the necessary dependencies for every service registration exists, and that each service registration can be successfully built. Using that capability of Lamar, you can now run IoC diagnostics for the application from the command line:
dotnet run -- lamar-validate
This command will fail if there are any missing registrations. I recommend using this as part of your system's continuous deployment process to “fail fast” while doing automated deployments.
Now, let's go a little farther with making the system self-diagnosing with Lamar. Up above, you built out a MarketDataSource
that needed to use a database connection string from the MarketOptions
object. Using an old trick left over from StructureMap, you're going to create an environment check directly on MarketDataSourc
e to verify that the system can correctly connect to the specified database, as shown below**:**
public class MarketDataSource : IMarketDataSource
{
private readonly MarketDataOptions _options;
public MarketDataSource(MarketDataOptions options)
{
_options = options;
}
[ValidationMethod]
public void CheckConnectivity()
{
using var conn = new SqlConnection(_options.ConnectionString);
conn.Open();
}
// Other methods
}
In the code above, the CheckConnectivity()
method is decorated with Lamar's [ValidationMethod]
attribute. This just tells Lamar that this method should be called during container diagnostic checks to further validate the application configuration. If the database connection string isn't correctly configured - or in my recent experience with erroneous database credentials or bad connection strings - this CheckConnectivity()
method will throw an exception, signaling to Lamar that the container as it is currently configured, and hence the application itself, is invalid and cannot run correctly. Now you run this command:
dotnet run -- lamar-validate
You quickly find out if the application as it's currently configured can access its market data dependency. Do note that Lamar will run through every single service registration and validated method when it validates the container and reports out all failures at once.
Lamar runs through every single service registration and validated method when it validates the container and reports out all failures at once.
This [ValidatedMethod]
environment check functionality can be useful to make your application verify:
- Required configuration items
- Expected access
- External dependencies
- Database access
- File system privileges
Service Registration Conventions
Lamar has strong support for both built-in service registration conventions and user-defined conventions for whatever you can dream up. Taking the example of the MarketDataSource
class that implemented a matching IMarketDataSource
interface using the common .NET idiom of sticking an “I” prefix on type names to denote an interface, you could use Lamar's default conventions to do the registration for you:
public void ConfigureContainer(ServiceRegistry services)
{
// Add basic type scanning of the main
// application assembly
services.Scan(s =>
{
s.TheCallingAssembly();
s.WithDefaultConventions();
});
// Other registrations
}
The type scanning looks through all the types in the designated assemblies and finds any combination of a concrete class that implements an interface where the interface name is the name of the class prefixed with an “I” and registers that concrete class against the interface with the default Transient
lifecycle.
That's the simplest possible usage and is commonly used. Moving on, the more powerful conventions may be for generic types. Let's say you're writing your own command handling framework like MediatR or Brighter (but please just use an off-the-shelf tool instead of rolling your own). The core abstraction is probably some sort of interface like this one, where the “T” is the actual message type:
public interface IHandler<T>
{
Task Handle(T message);
}
To handle a CreateOrder
command, you'd have an implementation like this:
public class CreateOrderHandler : IHandler<CreateOrder>
{
public Task Handle(CreateOrder order)
{
// do stuff using the order message
}
}
And a default handler for unknown messages like this:
public class DefaultHandler<T> : IHandler<T>
{
public Task Handle(T command)
{
// log the incoming command?
// throw an exception saying you
// don't support this command?
}
}
To set up auto-registration of all the IHandler<T>
implementations in the codebase with a fallback to the DefaultHandler<T>
for any command types that aren't explicitly handled by their own command handler, I'm going to use this Lamar ServiceRegistry
(Lamar's equivalent of StructureMap's old Registry or the .NET Core IServiceCollection
), as shown in 2.
Listing 2: HandlerRegistry
public class HandlerRegistry : ServiceRegistry
{
public HandlerRegistry()
{
Scan(x =>
{
// Tell Lamar which assemblies to scan
x.Assembly(Assembly.GetEntryAssembly());
x.ConnectImplementationsToTypesClosing(typeof(IHandler<>));
});
For(typeof(IHandler<>)).Use(typeof(DefaultHandler<>));
}
}
In HandlerRegistry
, I've set up a convention to look through the main application assembly's types and look for any type that closes the IHandler<T>
interface and registers the concrete class against the right interface. So the CreateOrderHandler
type in Listing 3 would be registered to the IHandler<CreateOrder>
interface. In the case of asking Lamar for an IHandler<Something>
where there is no explicit handler type for the Something
class, Lamar falls back to using DefaultHandler<Something>
because you made a registration for the open IHandler<T>
type.
Listing 3: MyService
public class MyService
{
private readonly IDataService _data;
private readonly IValidationService _validation;
public MyService(IDataService data) : this(data, new DefaultValidationService())
{
}
public MyService(IDataService data, IValidationService validation)
{
_data = data;
_validation = validation;
}
}
Back to the application's Startup.ConfigureContainer()
method, I'll include the service registrations from HandlerRegistry
with this one line of code:
public void ConfigureContainer(ServiceRegistry services)
{
services.IncludeRegistry<HandlerRegistry>();
// Other registrations
}
Type scanning and service registration conventions are what I call a “red pepper flakes” kind of tool. Adding a little bit of simple conventional registration can greatly reduce the amount of repetitive coding, but too much will pretty much always ruin the codebase. Use cautiously and judiciously.
Type scanning and service registration conventions are a “red pepper flakes” kind of tool.
Best Practices for DI Containers: One Constructor for the Best Results
Plenty of folks have written up their own best practices for DI containers, but I'm going to share some of mine based on 17 years and counting of supporting other developers using both StructureMap and Lamar. My personal best practices can be summed up as:
- Use a DI container so that there are few surprises at runtime.
- Use strong-typed configuration.
Use a DI Container So That There are Few Surprises at Runtime
Consider the example class named MyService
in Listing 3.
If this class is resolved out of a Lamar container, which constructor is going to be called? In the case of having multiple public constructors, Lamar will choose the “greediest” constructor with the most arguments that Lamar could resolve. Assuming that Lamar has a service registration for IDataService
:
- If the Lamar container has a registration for
IValidationService
, Lamar will use the constructor that takes in bothIDataService
andIValidationService
. - If Lamar doesn't know how to resolve
IValidationService
, Lamar will use theconstructor
function that only takes inIDataService
.
In this case. you can't decipher how Lamar will handle MyService
without knowing more about the rest of the container registrations, and this kind of ambiguity in container behavior frequently causes problems for folks using DI containers.
My strong preference and recommendation is that any type that's meant to be registered in a DI container should have one and only one public constructor. In this case, there should be no “optional” service registrations. In the case of the MyService
class, I'd opt to register a default IValidationService
in the container and simplify MyService
to just the one fat constructor, as shown below.
public class MyService
{
private readonly IDataService _data;
private readonly IValidationService _validation;
public MyService(IDataService data, IValidationService validation)
{
_data = data;
_validation = validation;
}
}
The Lamar usage could be simplified now to this:
var container = new Container(x =>
{
x.For<IDataService>().Use<DataService>();
x.For<IValidationService>().Use<DefaultValidationService>();
});
// The MyService class will be resolved using
// the default registration of both
// IDataService and IValidationService
var service = container.GetInstance<MyService>();
If you request a concrete type from Lamar that has no explicit service registrations and Lamar could resolve all of that concrete type's constructor dependencies, Lamar will quietly auto-resolve a registration for you and let you request that type. That's why there's no explicit registration up above for MyService
.
And by the way, thank the ASP.NET team for mandating this behavior from DI containers even though it's a frequent source of user confusion. This was an “opt in” behavior in StructureMap, but a default policy in Lamar due to the necessity of complying with the ASP.NET Core mandated behavior.
Use Strong-Typed Configuration
It's very common to need system configuration items like file paths or database connection strings within the types registered and built by a DI container. You could just inject the IConfiguration
for your system into a concrete type like I am in Listing 3 in a data service class.
public class ConfigUsingDataService : IDataService
{
private readonly string _connectionString;
public ConfigUsingDataService(IConfiguration configuration)
{
_connectionString = Configuration.GetConnectionString("database");
}
}
That's not entirely helpful, because now the ConfigUsingDataService
isn't usable without having a fully formed IConfiguration
with the necessary connection string as part of it. You could instead teach Lamar to inject the connection string directly into this class:
public class OneDataService : IDataService
{
private readonly string _connectionString;
// Where does this connection string come from?
public OneDataService(string connectionString)
{
_connectionString = connectionString;
}
}
```cs
And the Lamar registration shown below:
```cs
var container = new Container(x =>
{
// HostBuilder is going to do this
// for you behind the scenes in newer
// .NET applications
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
x.ForSingletonOf<IConfiguration>().Use(config);
x.For<IDataService>().Use(s =>
{
var config = s.GetInstance<IConfiguration>();
var connectionString = config.GetConnectionString("database");
return new OneDataService(connectionString);
});
});
Now the OneDataService
is decoupled from the system configuration, but using primitive types like strings, Uri
, or Guid
as constructor arguments can easily make Lamar more laborious to use and arguably make it harder to trace configuration usages.
My strong preference is to use strong typed configuration within the DI container. To illustrate that, I'll create a new AppSettings
class just to be a data holder for the system configuration that will be injected by a Lamar container:
public class AppSettings
{
public string ConnectionString { get; set; }
}
public class PreferredDataService : IDataService
{
private readonly AppSettings _settings;
public PreferredDataService(AppSettings settings)
{
_settings = settings;
}
}
Now, I can bind AppSettings
to the system configuration through the Lamar registration as shown below:
var container = new Container(r =>
{
// IConfiguration would be registered by
// the .NET HostBuilder
// This is a pretty easy way to bind
// AppSettings to IConfiguration without
// any other dependencies
r.ForSingletonOf<AppSettings>()
.Use(s => s.GetInstance<IConfiguration>()
.Get<AppSettings>());
// or alternatively with the .Net
// abstractions:
r.AddSingleton(s => s
.GetRequiredService<IConfiguration>()
.Get<AppSettings>());
// Or do it with the IOptions model
r.AddOptions<AppSettings>();
});
I find strong typed configuration more easily traceable in code. I also think that this style of configuration makes it easier to reuse code outside of the original application or within automated test code. The IOptions<T>
model is built into .NET now, so that option is also readily available if you're okay with that little bit of extra code ceremony.
Summary
Lamar is a relatively new Inversion of Control container tool that's a more powerful alternative to the built-in .NET ServiceProvider container. Lamar adds powerful diagnostics and conventional registration support that many users find to be vital for using an IoC tool in larger applications. If you're still using StructureMap, Lamar was built specifically to be a much more performant replacement for StructureMap and has a relatively easy upgrade path from StructureMap. As for best practices while using an IoC tool, the one sentence summary is to just use IoC tools in a simpler, surprise-free manner.