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 ‘DefaultUserPreferenceSettingsFilter’class
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
Post a Comment