Introduction
In many C#/.NET applications, business services rely on common dependencies like logging, mapping, time management, or database repositories.
Injecting these repeatedly into every service can lead to boilerplate code, inconsistency and less room for dependancies with relevant business logic value.
Another drawback of this is that potential code scanners like Sonar could eventually raise some rules errors when some of your constructors injects too much parameters (which is the case by default in Sonar C# rules, 7 max allowed ๐). And this rule break could potentially block your quality gate โ
To solve the issue, you can define a generic utility interface โ IServiceUtils<T>
โ that centralizes common dependencies and encourages consistent structure across services ๐ช
This is a pretty simple and straightforward pattern, and in this post I’ll show you what it is and how to implement it.
Let’s go ๐
What is IServiceUtils<T>
?
IServiceUtils<T>
is a generic utility interface designed to bundle commonly used services like:
ILogger<T>
โ for structured loggingIMapper
โ for object-to-object mappingTimeProvider
โ to get the current date/time in a testable wayIUnitOfWork
โ for managing transactions and repositories
This interface is then injected into your business services so they have access to all utilities via a single property.
Defining IServiceUtils<T>
public interface IServiceUtils<T>
{
ILogger<T> Logger { get; }
IMapper Mapper { get; }
TimeProvider TimeProvider { get; }
IUnitOfWork UnitOfWork { get; }
}
The interface is pretty simple.
T is used as a generic parameter in order to be processed by the ILogger.
Of course, you can add more common dependancies if you want to !
Dependancies that I show you today are some that I found useful in my current projects, but your implementation is up to you.
Just make sure that you don’t inject too much dependancies in your utils too, or you will just move your initial issue ๐
Implementing a Default ServiceUtils
public class ServiceUtils<T> : IServiceUtils<T>
{
public ILogger<T> Logger { get; }
public IMapper Mapper { get; }
public TimeProvider TimeProvider { get; }
public IUnitOfWork UnitOfWork { get; }
public ServiceUtils(
ILogger<T> logger,
IMapper mapper,
TimeProvider timeProvider,
IUnitOfWork unitOfWork)
{
Logger = logger;
Mapper = mapper;
TimeProvider = timeProvider;
UnitOfWork = unitOfWork;
}
}
The implementation is also straightforward !
Using IServiceUtils in a Business Service
- Without using
IServiceUtils
:
public class UserService
{
private readonly IUserRulesHelper _userRulesHelper;
private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
private readonly TimeProvider _timeProvider;
private readonly ILogger<T> _logger;
public UserService(
ILogger<T> logger,
IMapper mapper,
TimeProvider timeProvider,
IUnitOfWork unitOfWork,
IUserRulesHelper userRulesHelper)
{
_userRulesHelper = userRulesHelper;
_mapper = mapper;
_unitOfWork = unitOfWork;
_timeProvider = timeProvider;
_logger = logger;
}
public async Task<UserModel> CreateUserAsync(CreateUserModel createUserModel)
{
_logger.LogInformation("Creating user...");
var user = _mapper.Map<UserModel>(createUserModel);
_userRulesHelper.VerifyIsOverEighteen(user);
createUserModel.CreatedAt = _timeProvider.GetUtcNow();
await _unitOfWork.Users.AddAsync(createUserModel);
await _unitOfWork.CommitAsync();
return user;
}
}
- With
IServiceUtils
:
public class UserService
{
private readonly IServiceUtils<UserService> _utils;
private readonly IUserRulesHelper _userRulesHelper;
public UserService(IServiceUtils<UserService> utils, IUserRulesHelper userRulesHelper)
{
_utils = utils;
_userRulesHelper = userRulesHelper;
}
public async Task<UserModel> CreateUserAsync(CreateUserModel createUserModel)
{
_utils.Logger.LogInformation("Creating user...");
var user = _utils.Mapper.Map<UserModel>(createUserModel);
_userRulesHelper.VerifyIsOverEighteen(user);
createUserModel.CreatedAt = _utils.TimeProvider.GetUtcNow();
await _utils.UnitOfWork.Users.AddAsync(createUserModel);
await _utils.UnitOfWork.CommitAsync();
return user;
}
}
As the ServiceUtils takes a T param, add your implementation in your DI using this specific syntax:
services.AddScoped(typeof(IServiceUtils<>), typeof(ServiceUtils<>));
Here we can see that we reduced the numbers of parameters injected in the constructors from 5 to 2 in our UserService. ๐ฅณ
Thanks to this, the UserService has now more room to inject specific business related implementations, such as some user helpers or other implementations that contains real business value.
What are the benefits ?
-
Less Boilerplate: Avoid repeating constructor parameters in every service.
-
Consistency: All services get the same base dependencies in a unified way.
-
Testability: Can mock IServiceUtils easily in unit tests.
-
Extensibility: Add more dependencies in one place without changing all service constructors.
-
Responsability: Focus on your business logic, and reflect it in your ctor signature.
Conclusion
Sometimes, simple and obvious refactoring such as the pattern I showed you today can be easily missed, but those are the ones from which you get big benefits !
The IServiceUtils<T>
interface is a pretty simple way to solve common pain points in service design โ like duplicated constructor dependencies, inconsistent service structure, or difficulty writing unit tests.
By centralizing shared concerns, you make your services cleaner, more maintainable, and easier to test. ๐ฅณ
As always, Happy hacking ๐จโ๐ปโค๏ธ