I'm on a "Combine These Two NuGet Packages in Web API" kick lately, so let's keep that going! We're going to set up an ASP.NET Web API project that uses AutoMapper and StructureMap to provide us with mapping and injection-based repositories and controllers. I already covered how to get started with StructureMap in Setting Up Dependency Injection in ASP.NET Web API, so this post will focus more on what AutoMapper does and how to use it in Dependency Injection (DI) scenarios. Let's get started!
What is AutoMapper?
AutoMapper is a convention-based mapping library that maps values from one instance of an object to another. In most cases, the values will be mapped if the name and type of the properties match in both the source and destination objects. AutoMapper is also very flexible, allowing for you to design an entirely custom mapping rule set to support any scenario. In this post, we'll mostly use AutoMapper's default settings. For more information about one of my favorite NuGet packages ever, read the docs.
Models
First off, we need the models that will be mapped. In this post, I'm assuming that one of the models represents the database structure of the object, and another model represents the Data Transfer Object variation. Here's our models:
public class DbUser
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int AccessLevelID { get; set; }
public string Username { get; set; }
}
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public AccessLevel AccessLevel { get; set; }
public string Username { get; set; }
}
public enum AccessLevel
{
User,
Admin,
Configuration,
Test
}
Our Repositories will need to map objects of type DbUser
to objects of type User
. Let's see how that would look.
Repositories
Let's make an IUserRepository
interface that we can then use via dependency injection:
public interface IUserRepository
{
List<User> GetAll();
}
We now need to implement that interface, but the Repository will need to be able to map the two user objects. In order for that to happen, we need to be able to inject the IMapper interface into the repository, Here's our UserRepository
class:
public class UserRepository : IUserRepository
{
private IMapper _mapper;
public UserRepository(IMapper mapper)
{
_mapper = mapper;
}
public List<User> GetAll()
{
List<DbUser> dbUsers = new List<DbUser>()
{
new DbUser()
{
ID = 1,
Username = "Anorak",
FirstName = "James",
LastName = "Halliday",
AccessLevelID = 2
},
new DbUser()
{
ID = 2,
Username = "Great and Powerful Og",
FirstName = "Ogden",
LastName = "Morrow",
AccessLevelID = 2
},
new DbUser()
{
ID = 1020994,
Username = "Parzival",
FirstName = "Wade",
LastName = "Watts",
AccessLevelID = 1
}
};
return _mapper.Map<List<User>>(dbUsers);
}
}
But wait, don't we need to create a map for AutoMapper so that it knows how to map these objects? We do, but first, we need to set up StructureMap and add some plumbing.
Setting Up StructureMap
In order to set up StructureMap for use in a WebAPI application, I'm using the StructureMap.WebApi2 NuGet package. Said package adds several files to my project:
It turns out that I don't need all of these files to implement my solution. So let's remove StructuremapMvc.cs, StructuremapWebApi.cs and StructureMapScopeModule.cs. The resulting file structure looks like this:
Now we need to do some plumbing to get this structure working. In the App_Start/WebApiConfig.cs file, we need to modify the Register method like so:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
IContainer container = IoC.Initialize();
GlobalConfiguration.Configuration.DependencyResolver = new StructureMapWebApiDependencyResolver(container);
}
In this method, we create a Dependency Injection container (IContainer
) and add it to the global configuration so that it can be used. But where do we tell the container what interfaces and classes to use? In the DefaultRegistry.cs file.
In StructureMap, a Registry is a class that initializes what classes can be injected via the Container. The DefaultRegistry initially looks like this:
public class DefaultRegistry : Registry {
#region Constructors and Destructors
public DefaultRegistry() {
Scan(
scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
//For<IExample>().Use<Example>();
}
#endregion
}
However, I don't want that Scan()
method. What it's supposed to do is automatically find injectable classes and add them to the Container, but I haven't found that it works all that well. Instead, I'll remove it for now, and we'll come back to it after we have set up AutoMapper.
Setting Up AutoMapper
Now let's go get the AutoMapper package. That package doesn't add any new files, so we need to add them ourselves.
AutoMapper works by defining "maps" that map properties from one object type to another. It does this by inspecting each type and assuming that properties which match their type and name will be mapped from one type to the other. Problem is, it doesn't create any of these maps automatically; we have to create them via the use of Profiles.
Let's create a Profile class for AutoMapper and initialize a map that maps DbUser
to User
:
public class UserProfile : Profile
{
protected override void Configure()
{
CreateMap<DbUser, User>().ForMember(dest => dest.AccessLevel, opt => opt.MapFrom(src => (AccessLevel)src.AccessLevelID));
}
}
Note that we had to explicitly define what property DbUser.AccessLevelID
mapped to. This is because the properties do not match type, so in this case the map is a simple conversion to an enum.
Once we've got the profile defined, we need StructureMap to include it in the DI container. We can do this many ways, but the way I've found to be the most helpful involves editing the DefaultRegistry.cs file from earlier like so:
public class DefaultRegistry : Registry {
public DefaultRegistry() {
//Get all Profiles
var profiles = from t in typeof(DefaultRegistry).Assembly.GetTypes()
where typeof(Profile).IsAssignableFrom(t)
select (Profile)Activator.CreateInstance(t);
//For each Profile, include that profile in the MapperConfiguration
var config = new MapperConfiguration(cfg =>
{
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
});
//Create a mapper that will be used by the DI container
var mapper = config.CreateMapper();
//Register the DI interfaces with their implementation
For<IMapperConfiguration>().Use(config);
For<IMapper>().Use(mapper);
}
}
We still have an issue we need to solve, though. Remember the UserRepository
from earlier? That class expects to receive an IMapper
through its constructor. We need to tell the DI container that any time a class requires an object of type IUserRepository
, we need to give them a class of type UserRepository
. In the DefaultRegistry, we can do that like this:
public class DefaultRegistry : Registry {
public DefaultRegistry() {
...
//Register the DI interfaces with their implementation
For<IMapperConfiguration>().Use(config);
For<IMapper>().Use(mapper);
//Register the UserRepository and pass instance of Mapper to its constructor
For<IUserRepository>().Use<UserRepository>()
.Ctor<IMapper>().Is(mapper);
}
}
Phew! That's a lot of plumbing we needed to do! But it'll all be worth it, since creating a controller is now stupid easy.
Creating a DI-Enabled Controller
Let's create a UserController
class that will serve requests. We need to pass an instance of UserRepository
in the controller's constructor, and we need an action that returns all users.
[RoutePrefix("users")]
public class UserController : ApiController
{
private IUserRepository _userRepo;
public UserController(IUserRepository userRepo)
{
_userRepo = userRepo;
}
[Route("all")]
[HttpGet]
public IHttpActionResult GetAll()
{
return Ok(_userRepo.GetAll());
}
}
Earlier, we needed to explicitly include the repositories into the DI container. We do not do the same for the controllers, since we already implicitly took care of that in the plumbing. All that's left to do now is test the controller.
Testing the Controller
Let's test the controller using Postman. The URL we need to hit is localhost:
Which is exactly what we expected. The system is now fully DI-enabled, and AutoMapper is correctly producing the maps and delivering them to the repositories, where the actual mapping occurs.
Summary
This structure allows us to use AutoMapper and StructureMap in concert, creating extendable, testable, reusable projects at the expense of a lot of plumbing work up front. The steps we followed to enable AutoMapper and StructureMap were:
- Create the Models
- Create the Repositories
- Add StructureMap.WebApi2 NuGet package.
- Remove unnecessary StructureMap files.
- Add the DI Container to the WebAPI configuration.
- Register the Repositories with StructureMap.
- Add AutoMapper and Profiles.
- Inject
IMapper
into Repositories.
If you liked this post, check out the sample project, and let me know in the comments!
Happy Coding!