Inject Default Settings in ASP.Net Core through Action Filters


Introduction

There are use cases where a default setting to be used if the settings are not passed in API in a MVC web application. In a model-view-controller pattern, the API request is structured in a model and the model contains some settings which may not mandatory, but still, we need to have default values int it. This article explores about the pattern to inject the default setting into the model in an elegant way and efficient way.

Where to inject the default settings?

Let us assume a scenario where we need to create a new user profile through an API. So we create an API ’/user’ and the request model ‘UserProfile’. The model has ‘FirstName’, ‘LastName’ etc, and it also has ‘Preferences’ where a user provides certain preferences like language, theme etc. Below code shows the ‘UserProfile’ model.

namespace DefaultValueInjection.Model

{

    public class UserProfile

    {

        public string FirstName {  get; set; }

        public string LastName {  get; set; }

 

        public Preference Preference { get; set; }

    }

 

    public class Preference

    {

        public string Language { get; set; }

        public string Theme { get; set; }

    }

}

Listing1: UserProfile Model

In the above model the ‘Preferences’ need not to be mandatory and most of the time user don’t have to provide these data. But for our code flow we need these values. These default values should be injected into the model even though user don’t provide those details. Where do we inject the default values into the model?

One way is to have a constant class and hardcode the default values and assign it in our controller. But hardcoding as constant is not a best practice. We can have the default value in an appsettings.json and inject the values through IOptions to the controller. 

  "DefaultSettings": {

    "Language": "en",

    "Theme":  "Black"

  }

Listing2: Default settings in appsettings.json

How it would be if we inject the default values within the model itself?

Using Action Filters

Action filters is one of the elegant ways to inject the default values directly into the models. This approach also separates the logic into a separate class and keeps the controller clean.

Here is what our POST user API will look like,

    [ApiController]

    [Route("[controller]")]

    public class UserController : ControllerBase

    {

        [ServiceFilter(typeof(DefaultUserPreferenceSettingsFilter))]

        [HttpPost]

        public ActionResult AddUser([FromBody]UserProfile userProfile)

        {

 

            return Ok(userProfile);

        }

    }

Listing3: POST user API request structure

As you would have noticed that we have decorated the ‘AddUser’ method with the service filter ‘DefaultUserPreferenceSettingsFilter’ filter. This filter does the default settings injection into the ‘userProfile request model.

The controller now looks simple and elegant and all the default settings logic are offloaded to ‘DefaultUserPreferenceSettingsFilter’ class. Now it doesn’t matter if the preference is send by request or fetched from ‘appsettings.json’, our method looks the same.

Now let us look at the ‘DefaultUserPreferenceSettingsFilter’ filter,

public class DefaultUserPreferenceSettingsFilter : ActionFilterAttribute

    {

        readonly DefaultSettings _defaultSettings;

 

        public DefaultUserPreferenceSettingsFilter(IOptions<DefaultSettings> defaultSettings)

        {

            _defaultSettings = defaultSettings.Value;

        }

 

        public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)

        {

            var request = context.ActionArguments.Values.OfType<UserProfile>().FirstOrDefault();

            if (request != null)

            {

                var preference = request.Preference;

 

                if (preference != null)

                {

                    //check the request contains the preference values if not substitute the default values from default settings

                    if (string.IsNullOrEmpty(preference.Language)) preference.Language = _defaultSettings.Language;

                    if (string.IsNullOrEmpty(preference.Theme)) preference.Theme = _defaultSettings.Theme;

                }

                else

                {

                    request.Preference = new Preference { Language = _defaultSettings.Language, Theme = _defaultSettings.Theme };

                }

            }

 

            return base.OnActionExecutionAsync(context, next);

        }

    }

Listing4: DefaultUserPreferenceSettingsFilter class

The ‘DefaultUserPreferenceSettingsFilterclass is a filter and, in the class, ‘defaultSettings is injected through the constructor and in the ‘OnActionExecutionAsync’ method we check the request contains the value for preferences, if not we will substitute with the default values.

Testing the Filter

Here it the ‘curl’ command to invoke the ‘user’ api,

curl -X POST "https://localhost:5001/User" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"firstName\":\"Arun\",\"lastName\":\"Venkatesan\"}"

In the request we just passed only the firstName and lastname and the preference is entirely left out.

The below screen grab shows the ‘userProfile’ model is filled with default values,

Image1: User profile with default values

Sample Code

The sample code for the above is found at the github.

Comments

Popular posts from this blog

Debugging and Testing Helm Charts Using VS Code

Handle Multipart Contents in Asp.Net Core

Validate appsettings in ASP.net Core using FluentValidation