Dependency Injection in C# is a lifesaver when organizing dependencies — especially in more complex ASP.NET Core applications. And if you’re already familiar with IServiceCollection or just want to stick as close as possible to the already provided DI offerings, Scrutor in C# is a great enhancement.
In this article, I’ll provide you with a high-level overview of Dependency Injection and Scrutor in C#. But from there, we’re jumping right into 3 tips that you can use with Scrutor that could prove to be very helpful in your application!
What’s In This Article: Scrutor in C#
What is Scrutor in C#?
Scrutor is a powerful NuGet package in C# that enhances dependency injection. It simplifies the registration and resolution of dependencies, making it easier for you to manage and organize your code.
Dependency injection is a popular approach to configuring applications that promotes loose coupling and improves testability and maintainability. It involves injecting dependencies into a class rather than directly creating them within the class. This means that the responsibility of creating the dependency is not owned by the class requiring it, but instead, owned by someone up the call stack. Eventually, this pushes nearly all dependency creation to the entry point of an application, making it unwieldy. But dependency injection frameworks help clean up and organize all of this logic.
Scrutor takes this concept a step further by providing a simple and intuitive way to register and resolve dependencies. With Scrutor, you no longer need to manually register each dependency one by one. Instead, you can use conventions and attributes to automate the process.
1 – Using Scrutor for Registration and Resolution
To illustrate how to use Scrutor in C# for registration and resolution, let’s consider a simple scenario. Imagine we have an application that requires the use of different data repositories, such as UserRepository
and ProductRepostiory
.
First, we need to install the Scrutor NuGet package in our project. Open the Package Manager Console and run the following command:
Install-Package Scrutor
Next, we need to define our repositories and their corresponding interfaces. This example is essentially empty, but we’ll be referring to these in a moment:
public interface IUserRepository
{
}
public class UserRepository : IUserRepository
{
}
public interface IProductRepository
{
}
public class ProductRepository : IProductRepository
{
}
Now, let’s wire up our dependencies using Scrutor. In your startup code (such as in the ConfigureServices
method in ASP.NET Core), add the following code:
services.Scan(scan => scan
.FromAssemblyOf<Startup>()
.AddClasses(classes => classes.AssignableToAny(
typeof(IUserRepository),
typeof(IProductRepository)))
.AsImplementedInterfaces()
.WithScopedLifetime());
This code uses the Scan
method from Scrutor to scan the assembly that contains the Startup
class. It then filters out the classes that are assignable to IUserRepository
or IProductRepository
interfaces. Finally, it maps the implementations of these interfaces and registers them with their respective interfaces.
Now, we can inject these dependencies into our classes. For example, let’s say we have a UserService
class that requires IUserRepository
:
public class UserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
}
By declaring IUserRepository
as a dependency in the constructor, Scrutor along with IServiceCollection
will automatically resolve and inject the UserRepository
instance for us.
2 – The Decorator Pattern – Example of Logging for Services
Imagine you have a service interface IService
and an implementation MyService
. You want to log the entry and exit of each method call in MyService
without polluting its business logic with logging concerns.
First, define the IService
interface and its implementation:
public interface IService
{
void DoWork();
}
public class MyService : IService
{
public void DoWork()
{
}
}
Next, create a decorator class LoggingDecorator
that implements IService
. This class will wrap the actual service and add logging around the delegated method calls:
public class LoggingDecorator : IService
{
private readonly IService _decorated;
private readonly ILogger<LoggingDecorator> _logger;
public LoggingDecorator(IService decorated, ILogger<LoggingDecorator> logger)
{
_decorated = decorated;
_logger = logger;
}
public void DoWork()
{
_logger.LogInformation("Starting work.");
_decorated.DoWork();
_logger.LogInformation("Finished work.");
}
}
Now, use Scrutor to register your service and its decorator in the Startup.cs or wherever you configure your services:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IService, MyService>();
services.Decorate<IService, LoggingDecorator>();
}
This setup uses Scrutor to wrap the IService
registration with the LoggingDecorator
. When resolving IService
, the DI container will provide an instance of LoggingDecorator
, which in turn wraps an instance of MyService
. This approach achieves the separation of concerns by keeping the logging logic outside of your business logic.
3 – Feature Toggle for Service Implementations
When we’re building complex systems, there are often cases where we want to introduce something like feature flags. These allow us to take different code paths based on configuration and not have to recompile and deploy an entire application. In some situations, it’s not just about picking a different code path, it’s about swapping an entire implementation of something!
Let’s try out an example together! Assume you have an IFeatureService
interface with multiple implementations: NewFeatureService
(a new, experimental feature) and StandardFeatureService
(the current, stable feature). You want to switch between these implementations at runtime based on a configuration flag.
First, define the IFeatureService
interface and its implementations:
public interface IFeatureService
{
void ExecuteFeature();
}
public class NewFeatureService : IFeatureService
{
public void ExecuteFeature()
{
}
}
public class StandardFeatureService : IFeatureService
{
public void ExecuteFeature()
{
}
}
Next, you need a way to determine which implementation to use based on a feature toggle setting. This could be a value in appsettings.json, a database setting, or any other configuration source.
Now, use Scrutor to dynamically register the appropriate service implementation based on the feature toggle:
public void ConfigureServices(IServiceCollection services)
{
var useNewFeature = GetFeatureToggleValue("UseNewFeature");
if (useNewFeature)
{
services.AddScoped<IFeatureService, NewFeatureService>();
}
else
{
services.AddScoped<IFeatureService, StandardFeatureService>();
}
}
private bool GetFeatureToggleValue(string featureName)
{
return false;
}
Taking an approach like this can help us with:
- Flexibility: Allows the application to switch between feature implementations without needing to redeploy. You can enable or disable features with just a configuration change.
- Testing in Production: You can test new features in a production environment with a limited set of users by toggling the feature on for those users.
- Gradual Rollouts: Helps in rolling out features gradually to monitor the impact and ensure stability before a wider release.
- Risk Mitigation: Quickly revert to the old implementation if issues arise with the new feature, minimizing downtime and impact on users.
Wrapping Up Scrutor in C#
In this article, I provided you with 3 simple tips for dependency injection with Scrutor in C#. After a brief overview of what dependency injection is and how Scrutor fits into it, we jumped right into our examples. We got to see assembly scanning, how to decorate dependencies, and even how to consider feature flagging entire implementations!
Dependency injection is something that can greatly simplify your applications as they grow in complexity, and Scrutor can be a huge help if you want to stick to the built-in IServiceCollection offering from Microsoft.
Frequently Asked Questions: Scrutor in C#
What is dependency injection?
Dependency injection is a design pattern in software engineering where the dependencies of a class are provided from external sources rather than being created internally.
Why is dependency injection important?
Dependency injection promotes loose coupling, improves testability, and enhances code reusability and maintainability.
What is Scrutor?
Scrutor is a lightweight library in C# that simplifies the process of registering and resolving dependencies using the built-in IServiceCollection dependency injection container.
How does Scrutor simplify dependency injection?
Scrutor provides a set of convenient extension methods that allow for automatic registration of dependencies based on conventions, reducing the need for explicit manual registration.
What are the benefits of using Scrutor?
Using Scrutor improves code readability, reduces boilerplate code, and enables better scalability and performance optimizations.