.NET Core Tutorial: Using the ServiceCollection Extension Pattern II: Pass options using the Options Pattern

In a previous post I demonstrated how to use to ServiceCollection Extension Pattern and how this prevents a bloated ConfigureServices() pipeline. In this follow up you will learn how to pass additional information to your implementation.

Consider the following extension method that adds your project containing some services:

public static class DataProjectServiceCollectionExtensions
{
    public static IServiceCollection AddServicesProject(this IServiceCollection services)
    {
        services.AddScoped<ICustomerService, CustomerService>();

        services.AddScoped<IArticleService, ArticleService>();

        return services;
    }
}

As seen in the previous post, we add these services to the IServiceCollection of the project by simply calling services.AddServicesProject(); in the ConfigureServices() pipeline in the startup.cs class.
Example service that gets data from a repository:

namespace Project.ServicesProject.Services
{
    internal sealed class ArticleService: ArticleService
    {
        private readonly IArticleRepository _repository;

        public ArticleRepository(IArticleRepository repository)
        {
            _repository = repository;
        }

        public async Task<Article[]> GetArticles()
            => await _repository.GetArticles();
    }

    internal interface ArticleService
    {
        Task<Article[]> GetArticles();
    }
}

In this example we want to pass a value from the hosting project’s appsettings.json to the services project. Let’s use an fixed StoreId in our project’s settings and we want to filter out the articles only for this specific store whenever we call GetArticles().

appsettings.json:

{
  "HttpsRedirection": {
    "Enabled": true
  },
  "StoreSettings": {
    "StoreId": 15
  },
}

We can pass this value through an options object that is used as a parameter in out extension method. We create the options preferably in the same folder as your extensions method. I add this to the root of the project where ServicesProjectServiceCollectionsExtension is located.

namespace Project.ServicesProject
{
    public sealed class ServicesProjectOptions
    {
        public int StoreId { get; set; }
    }
}

Now we have an value to get from the appsettings.json in the hosting project, we have an object to store this value, we have a different project that requires this value and all that remains is to pass this value to the new project, using the ServiceCollection Extension Pattern we implemented.

We adjust the extension method so it requires an Action<> delegate holding the options object. You can validate any additional attributes added to the properties of the options object if these are required.

public static class ServicesProjecterviceCollectionExtensions
{
    public static IServiceCollection AddServicesProject(this IServiceCollection services, , Action<ServicesProjectOptions> configureOptions)
    {
        services.AddOptions<ServicesProjectOptions>()
            .Configure(configureOptions)
            .ValidateDataAnnotations();

        services.AddScoped<ICustomerService, CustomerService>();

        services.AddScoped<IArticleService, ArticleService>();

        return services;
    }
}

Next step is to update the call to AddServicesProject() in your startup.cs file so that we read the storeId from the appsettings.json and pass it using the options object we just created for this.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddServicesProject(options =>
            {
                options.StoreId = configuration.GetValue<int>("StoreSettings:StoreId");;
            });
}

And that’s it. Now we can access the storeId in our ServicesProject and use it wherever needed. To get the storeId value in our we inject the IOptions<DataProjectOptions> (via Dependency Injection) in the service class and read the value from it. What we just implemented is another pattern called the Options Pattern. We can update the ArticleService as followed:

namespace Project.ServicesProject.Services
{
    internal sealed class ArticleService: ArticleService
    {
        private readonly IArticleRepository _repository;

        private readonly ServiceProjectOptions _options;

        public ArticleRepository(IArticleRepository repository, IOptions<ServicesProjectOptions> options)
        {
            _repository = repository;
            _options = options.Value;
        }

        public async Task<Article[]> GetArticles()
            => await _repository.GetArticles().Where(x => x.StoreId == _options.StoreId);
    }

    internal interface ArticleService
    {
        Task<Article[]> GetArticles();
    }
}

More information about the Option Pattern and additional know-how on validating options on the DOCS.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s