Sharing setting between actions in Oqtane Modules
ViewModel is your friend for sharing settings between actions.
The default Oqtane module template includes actions (aka Razor pages) for Index, Edit, and Settings. Typically, the Settings action stores data that is shared with the other actions. However, each action can maintain its own set of local variables for fetching and holding these settings. This can lead to duplicated code and potential bugs if one action changes without the others being updated. This post will show you how to solve these issues by creating a SettingsViewModel
.
In the initial template, we see a local _value
variable used to store the setting value for "SettingName." This name is quite generic. Let's make it more specific to our wiki module. We'll use two settings: Topic
and Category
, which will make up a wiki page. To manage these settings more effectively, I'll create a new SettingsViewModel
class and add public fields for Topic
and Category
.
namespace Marks.Module.Wiki
{
internal class SettingsViewModel
{
public string Topic { get; set; }
public string Category { get; set; }
}
}
Now, let's update the settings action to utilize our new SettingsViewModel
. This will help streamline our code and make it more maintainable.
// from the template
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
_value = SettingService.GetSetting(settings, "SettingName", "");
//using the viewmodel
var moduleSettings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
_settingsVM.Topic = SettingService.GetSetting(moduleSettings, "Topic", "");
_settingsVM.Category = SettingService.GetSetting(moduleSettings, "Category", "General");
It's a good start, but we still have some pesky literal strings for the settings and default values (that third parameter). To clean this up, let's extend our SettingsViewModel
to hold these values as well.
internal class SettingsViewModel
{
public static class Settings {
public static string Topic => nameof(SettingsViewModel.Topic);
public static string Category => nameof(SettingsViewModel.Topic);
}
public string Topic { get; set; } = "";
public string Category { get; set; } = "General";
}
var moduleSettings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
_settingsVM.Topic = SettingService.GetSetting(moduleSettings,
SettingsViewModel.Settings.Topic, _settingsVM.Topic);
_settingsVM.Category = SettingService.GetSetting(moduleSettings, SettingsViewModel.Settings.Category, _settingsVM.Category);
It's an improvement, but there's still a lot of typing and the action isn't very readable. It's time to make the ViewModel smarter by having it self-assemble. Let's move the reading and writing of services to the constructor.
internal class SettingsViewModel
{
public static class Settings
{
public static string Topic => nameof(SettingsViewModel.Topic);
public static string Category => nameof(SettingsViewModel.Topic);
}
public SettingsViewModel(ISettingService settingService,
Dictionary<string,string> moduleSettings)
{
Topic = settingService.GetSetting(moduleSettings, Settings.Topic,
Topic);
Category = settingService.GetSetting(moduleSettings, Settings.Category,
Category);
}
public string Topic { get; set; } = "";
public string Category { get; set; } = "General";
}
Now, some purists might argue that I’m missing a few elements of the pattern and this approach might not adhere to a typical ViewModel. Regardless, I’m calling it a ViewModel and defining it within the Module. The beauty of this approach is that any action in the module needing access to the settings can quickly and easily obtain it.
var moduleSettings = await SettingService.GetModuleSettingsAsync(
ModuleState.ModuleId);
_settingsVM = new SettingsViewModel(SettingService,moduleSettings);
For my money, this is a clean approach that centralizes all settings-related code. We've tried other methods, like creating a base class for the module, but this ViewModel approach feels the most intuitive and effective. I hope you find it useful as well.
Reference code can be found here: https://github.com/markdav-is/Oqtane-SettingsViewModel-Example