-
Notifications
You must be signed in to change notification settings - Fork 749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add IPortalSettingsManager for Abstractions Project #4071
Conversation
…PortalSettingsManager
public ISettingsService GetPortalSettings(int portalId, string cultureCode) | ||
{ | ||
if (string.IsNullOrEmpty(cultureCode) && portalId > -1) | ||
{ | ||
cultureCode = PortalController.GetActivePortalLanguage(portalId); | ||
} | ||
|
||
var cachedSettings = this.GetCachedSettings(portalId, cultureCode); | ||
var saveService = new PortalSaveSettingsService(portalId, cultureCode); | ||
var deleteService = new PortalDeleteSettingsService(portalId, cultureCode); | ||
|
||
return new PortalSettingsService(() => this.GetCachedSettings(portalId, cultureCode), saveService, deleteService); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be one of the focal points of this code review and I would like to highlight the change as well as my reasoning behind the change.
I wanted to implement the PortalSettingsService
as a very simple wrapper around an IDictionary<string, string>
. Since DNN has static API calls everywhere I didn't want the PortalSettingsService
to be tightly coupled to anything. In theory this implementation could live in a .NET Standard 2.0 project and doesn't need to exist in the DotNetNuke.Library
project. To do this I needed to create an ISaveSettingsService
and a IDeleteSettingsService
The implementations of this interfaces are passed in at instantiation time. This allows the wrapper (PortalSettingsService
) to easily invoke the functionality.
The other item here is the callback function that retrieves the latest settings GetCacheSettings(int, int)
. This is useful in the PortalSettingsService
to reset the local IDictionary<string, string>
whenever the cache is cleared. Due to the de-coupled structure we need a way for it to easily reload the data. Since the PortalSettingsService
doesn't have direct knowledge how to construct the IDictionary<string, string>
we are using the callback that stores the reference on how to load the data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ahoefling!
/// to be injected via Dependency Injection. Please use the | ||
/// <see cref="ISettingsService"/>. | ||
/// </summary> | ||
public interface ISaveSettingsService |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these interfaces be internal
if they're not intended to be used via DI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really great question!
In my opinion these interfaces should remain public if we want to support making the PortalSettingsService
extensible. If a developer wants to swap it out for their own implementation they should be able to use the following code.
public class CustomPortalSettingsManager : PortalSettingsManager { }
public class CustomPortalSaveSettingsService : PortalSaveSettingsService { }
public class CustomPortalDeleteSettingsService : PortalDeleteSettingsService { }
If the extension is just changing the save/delete rules it doesn't necessarily need to create another class for the PortalSettingsService
.This approach gives us the highest degree of extensibility, but also makes it more complicated.
For many modules and implementations this will never be used. I see this being valuable for something like Evoq or custom installations that want to execute special logic during the save or delete sequence.
/// </summary> | ||
/// <param name="key">The setting key to update.</param> | ||
/// <param name="value">The value to set.</param> | ||
void Update(string key, string value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want generic Update APIs to match the generic Get APIs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the update API support full C# objects that get serialized into json or should it just support primitives such as
- string
- int
- double
- float
- long
- short
- bool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WOW! That sounds really powerful, I will certainly swap out this implementation for that. Should we migrate that to the abstractions project as part of this PR. That sounds like something really useful to be available via Dependency Injection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're up for it, that would make sense to me. We can also do that as a separate PR if it makes sense to reduce the scope of this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll submit a new PR for the SerializationController
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summited PR #4087
|
||
/// <inheritdoc /> | ||
public void DeleteAll() => | ||
this.DeleteAll(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect clearCache
to default to true
/// <inheritdoc /> | ||
public void UpdateEncrypted(string key, string value, bool clearCache) | ||
{ | ||
this.Update(key, value, true, clearCache); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the encryption happen when calling this overload?
cultureCode = PortalController.GetActivePortalLanguage(portalId); | ||
} | ||
|
||
var cachedSettings = this.GetCachedSettings(portalId, cultureCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This instance isn't getting used, right?
var cachedSettings = this.GetCachedSettings(portalId, cultureCode); |
|
||
/// <inheritdoc /> | ||
public void Update(string key, string value) => | ||
this.SaveService.Update(key, value, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, I would expect clearCache
to default to true
try | ||
{ | ||
// DNN uses special logic for boolean. It accepts Y and TRUE. We should make sure that is convertible | ||
return (T)Convert.ChangeType(this[key], typeof(T)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where I would like to see the logic from SerializationController
reused (which will handle the special Boolean logic, for example).
var portalSettingsManager = Globals.DependencyProvider.GetRequiredService<IPortalSettingsManager>(); | ||
return portalSettingsManager | ||
.GetPortalSettings(portalID) | ||
.Get<string>(settingName) ?? defaultValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous logic is to return the defaultValue
when the value is an empty string, is this an intentional change to only return defaultValue
when the value is null
?
{ | ||
string currentSetting = GetPortalSetting(settingName, portalID, string.Empty, cultureCode); | ||
|
||
if (currentSetting != settingValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this check for the new value being equal to the old value still in the new implementation? I don't remember seeing something like that.
} | ||
} | ||
|
||
EventManager.Instance.OnPortalSettingUpdated(new PortalSettingUpdatedEventArgs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this event still being called?
Since #4087 and #4159 have been merged this PR can be updated to reflect the latest and greatest code changes. Here is my list of things to do
I am not 100% sure, but I think I am going to flatten the structure here as the PR is as written is kind of complicated and unnecessary |
We have detected this issue has not had any activity during the last 90 days. That could mean this issue is no longer relevant and/or nobody has found the necessary time to address the issue. We are trying to keep the list of open issues limited to those issues that are relevant to the majority and to close the ones that have become 'stale' (inactive). If no further activity is detected within the next 14 days, the issue will be closed automatically. |
Thanks for this contribution, given the time and other shifts these proposed changes have been noted, but a new pull request will be created if necessary to merge elements into the new 10.0 release plan. |
Related to #3985
Summary
Adds a new interface to Dependency Injection
DotNetNuke.Abstractions.Portals.IPortalSettingsManager
which is used for retrieving the current portal settings. The API structure in this PR has been simplified to use modern techniques in .NET such as generics and indexers. It still maintains the main functionalityUsage
This PR takes a few different interfaces and combines their implementations to work together. It will be consumed via dependency injection using the
IPortalSettingsManager
.The example above uses the service locator pattern, it is always recommended to use constructor injection where possible.
Missing Items
Currently this PR is missing a few parts of the implementation. I submitted it early to get feedback prior on the general direction. Below is a list of missing features that I plan to implement after the initial review.
IConfigurationSetting
provides a Key-Value-Pair object which is used throughout DNNRequired to merge
This PR was submitted prior to working on Unit Tests or validating the build works. I am doing this to get early feedback on architectural decisions being made with the changes proposed here.