Depending on Runtime Data

Being assigned to do some refactoring of some controller actions at work, one of the tasks was to simplify how we got values from the request.

I was a bit surprised how we actually got values from the request. While not remembering how it exactly looked like, it was something along the lines of the following:

1
2
3
4
5
6
7
8
9
10
[Authorize, HttpGet, Route("api/comments/{id:int}"]
public IHttpActionResult GetComment(int id)
{

var username = Request.Current.User.Identity.Name;
var source = Request.Headers.GetValues("Source").FirstOrDefault();

var repository = new CommentRepository(username, source);
var comment = repository.Get(id);
return Ok(comment);
}

In my opinion you should never inject runtime values to any components, as they are an implementation detail.

Don’t inject runtime data into components during construction; it causes ambiguity, complicates the composition root with an extra responsibility and makes it extraordinarily hard to verify the correctness of your DI configuration. My advice is to let runtime data flow through the method calls of constructed object graphs. – Steven van Deursen.

For the example above, we can define an abstraction which is accessible from all the repositories and services required:

1
2
3
4
5
6
public interface IUserContext
{
string Username { get; }

string Source { get; }
}

And implement this IUserContext in our composition root of our Web API application:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AspNetHttpUserContext : IUserContext
{
private readonly HttpRequestMessage _httpRequestMessage;

public AspNetHttpUserContext()
{

_httpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
}

public string Username => HttpContext.Current.User.Identity.Name;

public string Source => _httpRequestMessage.Headers.GetValues("Source").FirstOrDefault();
}

This means we can greatly simplify the CommentRepository from having a constructor which took two string to having one IUserContext:

1
2
3
4
5
6
7
8
9
10
11
12
public class CommentRepository : IRepository<Comment>
{
private readonly IUserContext _userContext;

public CommentRepository(IUserContext userContext)
{
if (userContext == null) throw new ArgumentNullException(nameof(userContext));
_userContext = userContext;
}

/* Methods for repository */
}

And our controller action is much more lean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CommentsController : ApiController {

private readonly IRepository<Comment> _commentRepository;

public CommentsController(IRepository<Comment> commentRepository) {
_commentRepository = commentRepository;
}

[Authorize, HttpGet, Route("api/comments/{id:int}"]
public IHttpActionResult GetComment(int id)
{

var comment = _commentRepository.Get(id);
return Ok(comment);
}

}

Since we receive the IUserContext per web request, we can easily configure our IRepository<Comment> and IUserContext in our Web API composition root:

1
2
3
// Using Simple Injector
container.RegisterPerWebRequest<IUserContext, AspNetHttpUserContext>();
container.RegisterPerWebRequest(typeof(IRepository<Comment>), typeof(CommentRepository)>();

Hopefully the next refactoring will be converting the application to the CQRS pattern. :-)

Comments