Service Collection Extension Pattern in .NET Core with Item Services

DotNet Full Stack Dev
4 min readSep 10, 2024

--

In .NET Core, the Service Collection Extension Pattern is a clean and maintainable way to organize service registrations in the dependency injection (DI) container. Instead of having a cluttered Startup.cs file (or Program.cs in newer versions), this pattern encourages separating service configurations into extension methods that extend the IServiceCollection interface.

This approach promotes modularity, reusability, and testability by grouping related service registrations into logically cohesive units.

Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.

Why Use the Service Collection Extension Pattern?

  1. Cleaner Startup/Program Files: It avoids having all service registrations in one place, making your Startup or Program files cleaner and easier to maintain.
  2. Modularity: You can group service registrations by feature or module, making your application more modular and easier to extend.
  3. Reusability: If you have common services across multiple projects, you can reuse your extension methods by placing them in a shared library.
  4. Testability: Extension methods are easier to mock or replace during unit testing, as you can inject different services based on different environments or configurations.

How to Implement the Service Collection Extension Pattern

Step 1: Create an Extension Method for Service Registration

The first step is to create an extension method for IServiceCollection that handles the registration of services related to a specific module or feature.

Here’s an example of creating an extension method to register services related to an Item API in a typical .NET Core application:

using Microsoft.Extensions.DependencyInjection;

namespace MyApp.Extensions
{
public static class ServiceCollectionExtensions
{
// This method extends IServiceCollection and registers services related to the Item API
public static IServiceCollection AddItemServices(this IServiceCollection services)
{
// Register services related to the Item API
services.AddScoped<IItemService, ItemService>();
services.AddTransient<IItemRepository, ItemRepository>();

// Add other services, for example, a scoped service for managing Item operations
services.AddScoped<IItemManager, ItemManager>();

return services; // Return the IServiceCollection for method chaining
}
}
}

In this example:

  • We create a static class ServiceCollectionExtensions that contains the extension method AddItemServices.
  • This method registers services like IItemService, IItemRepository, and IItemManager.
  • It returns the IServiceCollection, enabling method chaining.

Step 2: Use the Extension Method in Startup.cs or Program.cs

Once you have defined the extension method, you can call it in your Startup or Program file to register the services for the Item API.

Example for Program.cs (for .NET 6 and later):

using MyApp.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Add services to the DI container
builder.Services.AddItemServices(); // Use the extension method to register Item services

var app = builder.Build();

// Configure the HTTP request pipeline
app.MapControllers();

app.Run();

Here, builder.Services.AddItemServices() uses the extension method to register all the services related to the Item API. This keeps your Program.cs clean and modular.

Example for Startup.cs (for .NET 5 and earlier):

using MyApp.Extensions;

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Use the extension method to register Item services
services.AddItemServices();

services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}

Structuring Extension Methods

You can create multiple extension methods based on different functionalities or modules in your application. For instance:

Database-Related Services:

public static IServiceCollection AddDatabaseServices(this IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer("YourConnectionString"));
return services;
}

Caching Services:

public static IServiceCollection AddCachingServices(this IServiceCollection services)
{
services.AddMemoryCache();
services.AddDistributedRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "MyApp_";
});
return services;
}

Authentication Services:

public static IServiceCollection AddAuthenticationServices(this IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Authority = "https://example.com";
options.Audience = "api";
});
return services;
}

Best Practices for Using the Service Collection Extension Pattern

  1. Group Related Services: Always group service registrations by feature or responsibility (e.g., all Item-related services in one method, all User-related services in another).
  2. Avoid Huge Methods: If your extension method is becoming too large, break it down into smaller, more focused methods to keep it manageable.
  3. Use Fluent API: Return the IServiceCollection in your extension methods to allow method chaining, which improves readability in the Startup or Program files.
  4. Namespace Organization: Keep the extension methods in well-organized namespaces (e.g., MyApp.Extensions) to avoid cluttering your primary application code.
  5. Separation of Concerns: Keep your business logic separate from the DI registration logic. The extension methods should focus only on registering services, not implementing business functionality.

Example: Full Implementation

Let’s implement a complete example of using the Service Collection Extension Pattern for an Item API.

Step 1: Define Services and Repositories

public interface IItemService
{
Task<Item> GetItemByIdAsync(int id);
}

public class ItemService : IItemService
{
private readonly IItemRepository _itemRepository;

public ItemService(IItemRepository itemRepository)
{
_itemRepository = itemRepository;
}

public async Task<Item> GetItemByIdAsync(int id)
{
return await _itemRepository.GetItemAsync(id);
}
}

public interface IItemRepository
{
Task<Item> GetItemAsync(int id);
}

public class ItemRepository : IItemRepository
{
public async Task<Item> GetItemAsync(int id)
{
// Assume database logic here
return await Task.FromResult(new Item { Id = id, Name = "Sample Item" });
}
}

public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}

Step 2: Create Extension Method

namespace MyApp.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddItemServices(this IServiceCollection services)
{
services.AddScoped<IItemService, ItemService>();
services.AddTransient<IItemRepository, ItemRepository>();
return services;
}
}
}

Step 3: Register Services in Program.cs

using MyApp.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Register services using the extension method
builder.Services.AddItemServices();

var app = builder.Build();

app.MapControllers();

app.Run();

Conclusion

The Service Collection Extension Pattern is a clean, modular, and reusable way to manage your service registrations in .NET Core. By extending IServiceCollection, you can keep your main configuration files (Startup.cs or Program.cs) clean, make your service registrations more modular, and enhance the maintainability of your application. This pattern is highly recommended for large applications or any scenario where you want better organization and flexibility in how services are configured and registered.

--

--

DotNet Full Stack Dev
DotNet Full Stack Dev

Written by DotNet Full Stack Dev

Join me to master .NET Full Stack Development & boost your skills by 1% daily with insights, examples, and techniques! https://dotnet-fullstack-dev.blogspot.com

Responses (1)