Registering components in Autofac is straightforward, as long as no primitive dependencies (such as connection strings, URLs and configuration parameters in general) are involved. This post describes the strategy for dealing with primitive dependencies.
- The Ordinary Case
- Here Come The Primitives
- Pain Points
- Service Locator Is The Wrong Solution
- Overcoming The Primitive Obsession
- Value Objects In Action
implicit
andexplicit
To The Rescue- Putting It All Together
- Conclusion
Say you have a class Foo
depending on Bar
:
class Foo
{
public Foo(Bar bar) { }
}
class Bar {}
Registering both of them with Autofac is just simple as writing:
var builder = new ContainerBuilder();
builder.RegisterType<Foo>();
builder.RegisterType<Bar>();
This is enough for Autofac: since it knows how to create instances of Bar
(it's just a matter of invoking its default constructor), it also knows how to build instances of Foo
.
This works with no additional configurations even if dependencies are reversed (e.g Bar
depends on Foo
) or if relationships are implicit, for example when Foo
depends on Func<Bar>
, or on List<Bar>
and the like: Autofac is smart enough to build an instance of the right class and inject it into the right component.
Troubles come when one of the dependencies is a primitive. Suppose that Foo
also depends on a connection string, which you decided to represent as a string
:
class Foo
{
public Foo(Bar bar, string connectionString) { }
}
You are offered a bunch of native Autofac facilities for registering this class.
Either you can use a lambda:
builder.Register(c =>
{
string connectionString = "someConnectionString";
var bar = c.Resolve<Bar>();
return new Foo(bar, connectionString);
});
or you can continue using the ordinary RegisterType<>()
, enhanced with the withParameter()
facility:
builder.RegisterType<Foo>()
.WithParameter("connectionString", "someConnectionString");
This is tollerable as long as there are just a very little number of primitive dependencies.
It starts stinking when it ends up with code like the following:
builder.Register(c =>
{
string connectionString = "someConnectionString";
string url = "http://some.url";
string maxUsers = 19;
var bar = c.Resolve<Bar>();
var baz = c.Resolve<Baz>()
return new Foo(bar, baz, connectionString, url, maxUsers);
});
or
builder.RegisterType<Foo>()
.WithParameter("connectionString", "someConnectionString")
.WithParameter("url", "http://some.url")
.WithParameter("maxUsers", 19);
Not only is it verbose and repetitive, but the resulting code is also affected by a couple of problems:
- both the URL and the connection string are represented with the same class (a string), giving no chances to the compiler to know which is which; consequently, it is very possible to switch their values without giving the receiving class the opportunity to detect the issue but at runtime.
Would you spot the problem in the following code?
class Foo
{
public Foo(Bar bar, Baz baz, string connectionString, string url, int maxUsers) { }
}
builder.Register(c =>
{
var bar = c.Resolve<Bar>();
var baz = c.Resolve<Baz>()
return new Foo(bar, baz, "http://some.url", "someConnectionString", 19);
});
The compiler wouldn't, and that's a pity;
withParameter
references parameters by name, as a string, so simple refactoring tasks such as renaming variables become very fragile.
The following code compiles, but it will throw an exception at runtime the moment you will try to resolveFoo
:
class Foo
{
public Foo(Bar bar, Baz baz, string connectionString, string url, int maxUsers) { }
}
builder.RegisterType<Foo>()
.WithParameter("connectionstring", "someConnectionString")
.WithParameter("url", "http://some.url")
.WithParameter("maxUsers", 19);
Yes, it's just a matter of a capitalized S
. Hard to spot, isn't it?
The bad habit of using primitive types to represent domain ideas is a smell called Primitive Obsession.
Let's see how to avoid it without endng up with ugly Autofac registration statements.
Why do you need configuration parameters, in the first place? Of course because you want the freedom to change them at runtime, presumably by using a configuration file:
var connectionString = ConfigurationManager.AppSetting["myConnection"];
builder.Register(c =>
{
var bar = c.Resolve<Bar>();
return new Foo(bar, connectionString);
});
This may suggest you a trick you could be tempted to use: to directly inject ConfigurationManager.AppSetting
into your classes:
class Foo
{
public Foo(Bar bar, NameValueCollection config)
{
var connectionString = config["myConnection"];
}
}
builder.RegisterType<Foo>();
builder.RegisterInstance(ConfigurationManager.AppSetting).As<NameValueCollection>();
Voilà. No more primitives!
You could even be inclined to define a custom service for collecting all of your configuration parameters:
class MyConfigs
{
public T Get<T>(string key)
{
...
}
public void LoadFromConfigfile()
{
...
}
}
builder.RegisterType<Foo>();
builder.RegisterInstance(new MyConfig().LoadFromConfigfile());
so that you can easily inject it, as a glorified configuration parameters repository:
class Foo
{
Foo(Bar bar, MyConfigs conf)
{
var maxUsers = conf.Get<int>("maxUsers");
}
}
This seems to solve most of the problems related to Primitive Obsession, right?
Well, yes, at least it solves the ugly Autofac registration statements issue. In fact, it introduces some additional problems, possibly worse than the ones it was supposed to solve.
The problem is: that's a Service Locator.
I strongly suggest you to read the seminal Mark Seemann's post Service Locator Is An Anti-Pattern which collects a lot of strong arguments on why you should avoid using the Service Locator pattern. Basically, the root of Service Locator's evil is that it hides class dependencies, causing run-time errros and maintenance additional burden.
Service Locator pattern is mostly related to services, while here you are dealing with values without behaviour, simple strings and integers; yet Mark Seemann's argument apply: injecting a configuration-parameters locator is an anti-pattern anyway.
Just don't do it.
Let me try to convince you that there is something deeply wrong with injecting a primitive.
Say you have 2 configuration parameters: maxUsers
and numberOfItemsPerPage
. They can be defined in 2 completely different contexts, represent 2 completely different ideas, and have nothing to share.
Yet you can represent both of them with the same type, int
.
That's the root error: when you use the very same class for 2 completely different purposes, it's just like collapsing CustomerController
and NHibernateSession
to a single class: by doing so, you would give no chance neither to Autofac nor to the compiler itself to distinguish the former from the latter.
It's easy to see why representing the customer controller and the NHibernate session with 2 dedicated classes is valuable: it isn't hard to see that the idea can be profitably applied to maxUsers
and numberOfItemsPerPage
well, and in general to any primitive configuration parameter. It would give you the opportunity the rely on the compiler: instead of having a constructor which takes 3 indistinguishable integers:
class Foo
{
public Foo(int maxDownloads, int itemsPerPage, int numberOfDays) { ... }
}
you would actually have 3 distinct parameters, each well defined with its specific class, just like all the other domain ideas:
class Foo
{
public Foo(MaxDownloads maxDownloads, ItemsPerPage itemsPerPage, NumberOfDays numberOfDays) { ... }
}
So, the basic trick for dealing with primitives in Autofac is: don't use primitives. Just represent your configuration parameters with non-primitive types.
Ok. Sounds simple.
So, instead of declaring maxUsers
and numberOfItemsPerPage
as int
, all you have to do is to define 2 separate non-primitive types inheriting from int
:
class MaxUsers : int {}
class NumerOfItemsPerPage : int {}
Unfortunately, this is not allowed in C#, since primitive types are sealed.
You definitely have to resort on a workaround: use a DTO as a wrapper of the primitive value.
class MaxUsers
{
public int Value { get; }
public MaxUsers(int value)
{
Value = value;
}
}
Think about it: isn't it exactly what you are already used to do, when dealing with compound configuration parameters? For example, chances are you had the need to inject into an instance the url, the username and the password for accessing a web service, and you decided to group them into a single DTO:
class BarServiceAuthParameters
{
public string Url { get; }
public string Username { get; }
public string AccessToken { get; }
public BarServiceAuthParameters(string url, string username, string accessToken)
{
Url = url;
Username = username;
AccessToken = accessToken;
}
}
Wasn't it easy to inject, since it's an ordinary class?
The trick is: use the same strategy also when dealing with single primitive values.
In the the very short post Primitive Obsession J.B. Rainsberger claims those kind of Value Object
[...] become "attractive code", meaning literally a class/module that attracts behavior towards it as new methods/functions. For example, instead of scattering all the parsing/formatting behavior for dates stored as text, introduce a DateFormat class which attracts that behavior as methods called parse() and format(), almost like magic.
So, it's likely that just by introducing a class for representing an URL or a connection string you will end up enhancing it with some formatting or validation logic, which you could not do with a plain, primitive string
.
Unfortunately, this solution has got it's drawbacks too. Now it's just more difficult to consume the ConnectionString
. You need to write:
connectionString.Value
instead of
connectionString
since it's not a string anymore.
It's also more difficult to assign it a value. Instead of
var connectionString = "foobar";
you need
var connectionString = new ConnectionString("foobar");
That's bad.
Let's see what you can do in order to make that Value Object resemble a primitive.
It would be nice if it were possible to implicitly cast it from and to string
.
Actually, that's not too hard to achieve. There is a technique Jimmy Bogard brillantly exposed in his post Dealing with primitive obsession that makes a smart use of the cast operators implicit
and explicit
and allows to make you consume and create your Value Objects as they are primitives.
Go and read the post. You will learn that by defining your Value Object as
public class ConnectionString
{
public string Value { get; private set; }
public ConnectionString(string value)
{
Value = value;
}
public static implicit operator string(ConnectionString connectionString)
{
return connectionString.Value;
}
public static implicit operator ConnectionString(string value)
{
return new ConnectionString(value);
}
}
you will get the benefit to consuming and creating it as a primitive, as in the following example:
class Foo
{
public ConnectionString Conn;
}
var foo = new Foo();
foo.Conn = "barbaz"; // implicitly converts from string to ConnectionString
string s = foo.Conn; // implicitly converts from ConnectionString to string
var z = (string) foo.Conn; // explicit conversion works as well
Once you define all your configuration parameters as Value Objects (with the implicit
trick), you can use them like the following:
class Foo
{
public Foo(Bar bar, URL url, ConnectionString conn, MaxdownloadableFiles max) { ... }
}
var builder = new ContainerBuilder();
builder.RegisterType<Bar>();
builder.RegisterType<Foo>();
builder.RegisterInstance((URL) "http://some.url");
builder.RegisterInstance((ConnectionString) "some connection string");
builder.RegisterInstance((MaxdownloadableFiles) 180);
// builder.RegisterInstance("some connection string").As<ConnectionString>(); // this doesn't work
- Don't use primitives. Use Value Objects;
- Avoid using the Service Locator pattern: it's bad;
- Define Value Objects wrapping your configurations, using the
implicit
cast operator to make them behave as a primitive; - Register them with
RegisterInstance()
.
You will get:
- easy and straightforward Autofac register statements, without using lambdas or
withParameter()
; - static compile time type checking on injected parameters (no more dependencies neither on parameters order nor on parameters names);
- easier refactoring, you can easily rename parameters or change the order of them, without breaking anything;
- all the advantages of using Value Objects, including the possibility to validate the primitive value;
- the above solution can used also for collection of primitives or classes, such as
List<string>
.