diff --git a/Oqtane.Server/Extensions/HttpContextExtensions.cs b/Oqtane.Server/Extensions/HttpContextExtensions.cs index c601297c..f5fc5597 100644 --- a/Oqtane.Server/Extensions/HttpContextExtensions.cs +++ b/Oqtane.Server/Extensions/HttpContextExtensions.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Oqtane.Models; using Oqtane.Shared; @@ -14,5 +15,14 @@ namespace Oqtane.Extensions } return null; } + + public static Dictionary GetSiteSettings(this HttpContext context) + { + if (context != null && context.Items.ContainsKey(Constants.HttpContextSiteSettingsKey)) + { + return context.Items[Constants.HttpContextSiteSettingsKey] as Dictionary; + } + return null; + } } } diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 80034928..ca7f6bb8 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -26,9 +26,9 @@ namespace Oqtane.Extensions public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder) { // site OpenIdConnect options - builder.AddSiteOptions((options, alias) => + builder.AddSiteOptions((options, alias, sitesettings) => { - if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OpenIDConnect) + if (sitesettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OpenIDConnect) { // default options options.SignInScheme = Constants.AuthenticationScheme; // identity cookie @@ -44,13 +44,13 @@ namespace Oqtane.Extensions options.CorrelationCookie.SameSite = SameSiteMode.Unspecified; // site options - options.Authority = alias.SiteSettings.GetValue("ExternalLogin:Authority", ""); - options.MetadataAddress = alias.SiteSettings.GetValue("ExternalLogin:MetadataUrl", ""); - options.ClientId = alias.SiteSettings.GetValue("ExternalLogin:ClientId", ""); - options.ClientSecret = alias.SiteSettings.GetValue("ExternalLogin:ClientSecret", ""); - options.UsePkce = bool.Parse(alias.SiteSettings.GetValue("ExternalLogin:PKCE", "false")); + options.Authority = sitesettings.GetValue("ExternalLogin:Authority", ""); + options.MetadataAddress = sitesettings.GetValue("ExternalLogin:MetadataUrl", ""); + options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); + options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); + options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false")); options.Scope.Clear(); - foreach (var scope in alias.SiteSettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries)) + foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries)) { options.Scope.Add(scope); } @@ -63,9 +63,9 @@ namespace Oqtane.Extensions }); // site OAuth2.0 options - builder.AddSiteOptions((options, alias) => + builder.AddSiteOptions((options, alias, sitesettings) => { - if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OAuth2) + if (sitesettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OAuth2) { // default options options.SignInScheme = Constants.AuthenticationScheme; // identity cookie @@ -73,14 +73,14 @@ namespace Oqtane.Extensions options.SaveTokens = true; // site options - options.AuthorizationEndpoint = alias.SiteSettings.GetValue("ExternalLogin:AuthorizationUrl", ""); - options.TokenEndpoint = alias.SiteSettings.GetValue("ExternalLogin:TokenUrl", ""); - options.UserInformationEndpoint = alias.SiteSettings.GetValue("ExternalLogin:UserInfoUrl", ""); - options.ClientId = alias.SiteSettings.GetValue("ExternalLogin:ClientId", ""); - options.ClientSecret = alias.SiteSettings.GetValue("ExternalLogin:ClientSecret", ""); - options.UsePkce = bool.Parse(alias.SiteSettings.GetValue("ExternalLogin:PKCE", "false")); + options.AuthorizationEndpoint = sitesettings.GetValue("ExternalLogin:AuthorizationUrl", ""); + options.TokenEndpoint = sitesettings.GetValue("ExternalLogin:TokenUrl", ""); + options.UserInformationEndpoint = sitesettings.GetValue("ExternalLogin:UserInfoUrl", ""); + options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); + options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); + options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false")); options.Scope.Clear(); - foreach (var scope in alias.SiteSettings.GetValue("ExternalLogin:Scopes", "").Split(',', StringSplitOptions.RemoveEmptyEntries)) + foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "").Split(',', StringSplitOptions.RemoveEmptyEntries)) { options.Scope.Add(scope); } @@ -118,7 +118,7 @@ namespace Oqtane.Extensions var regex = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase); foreach (Match match in regex.Matches(output)) { - if (EmailValid(match.Value, context.HttpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:DomainFilter", ""))) + if (EmailValid(match.Value, context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", ""))) { email = match.Value.ToLower(); break; @@ -139,7 +139,7 @@ namespace Oqtane.Extensions private static async Task OnTokenValidated(TokenValidatedContext context) { // OpenID Connect - var emailClaimType = context.HttpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:EmailClaimType", ""); + var emailClaimType = context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:EmailClaimType", ""); var email = context.Principal.FindFirstValue(emailClaimType); // login user @@ -171,14 +171,13 @@ namespace Oqtane.Extensions private static async Task LoginUser(string email, HttpContext httpContext, ClaimsPrincipal claimsPrincipal) { var _logger = httpContext.RequestServices.GetRequiredService(); - var alias = httpContext.GetAlias(); - if (EmailValid(email, alias.SiteSettings.GetValue("ExternalLogin:DomainFilter", ""))) + if (EmailValid(email, httpContext.GetSiteSettings().GetValue("ExternalLogin:DomainFilter", ""))) { var _identityUserManager = httpContext.RequestServices.GetRequiredService>(); var _users = httpContext.RequestServices.GetRequiredService(); var _userRoles = httpContext.RequestServices.GetRequiredService(); - var providerType = httpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:ProviderType", ""); + var providerType = httpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", ""); var providerKey = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier); if (providerKey == null) { @@ -189,7 +188,7 @@ namespace Oqtane.Extensions var identityuser = await _identityUserManager.FindByEmailAsync(email); if (identityuser == null) { - if (bool.Parse(alias.SiteSettings.GetValue("ExternalLogin:CreateUsers", "true"))) + if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:CreateUsers", "true"))) { identityuser = new IdentityUser(); identityuser.UserName = email; @@ -200,7 +199,7 @@ namespace Oqtane.Extensions { user = new User { - SiteId = alias.SiteId, + SiteId = httpContext.GetAlias().SiteId, Username = email, DisplayName = email, Email = email, @@ -212,7 +211,7 @@ namespace Oqtane.Extensions if (user != null) { var _notifications = httpContext.RequestServices.GetRequiredService(); - string url = httpContext.Request.Scheme + "://" + alias.Name; + string url = httpContext.Request.Scheme + "://" + httpContext.GetAlias().Name; string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!"; var notification = new Notification(user.SiteId, user, "User Account Notification", body); _notifications.AddNotification(notification); @@ -269,7 +268,7 @@ namespace Oqtane.Extensions var principal = (ClaimsIdentity)claimsPrincipal.Identity; UserSecurity.ResetClaimsIdentity(principal); List userroles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); - var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles); + var identity = UserSecurity.CreateClaimsIdentity(httpContext.GetAlias(), user, userroles); principal.AddClaims(identity.Claims); // update user diff --git a/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs index 820f0b22..d732421f 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs @@ -10,19 +10,19 @@ namespace Oqtane.Extensions public static OqtaneSiteOptionsBuilder WithSiteIdentity(this OqtaneSiteOptionsBuilder builder) { // site identity options - builder.AddSiteOptions((options, alias) => + builder.AddSiteOptions((options, alias, sitesettings) => { // password options - options.Password.RequiredLength = int.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequiredLength", options.Password.RequiredLength.ToString())); - options.Password.RequiredUniqueChars = int.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequiredUniqueChars", options.Password.RequiredUniqueChars.ToString())); - options.Password.RequireDigit = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireDigit", options.Password.RequireDigit.ToString())); - options.Password.RequireUppercase = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireUppercase", options.Password.RequireUppercase.ToString())); - options.Password.RequireLowercase = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireLowercase", options.Password.RequireLowercase.ToString())); - options.Password.RequireNonAlphanumeric = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireNonAlphanumeric", options.Password.RequireNonAlphanumeric.ToString())); + options.Password.RequiredLength = int.Parse(sitesettings.GetValue("IdentityOptions:Password:RequiredLength", options.Password.RequiredLength.ToString())); + options.Password.RequiredUniqueChars = int.Parse(sitesettings.GetValue("IdentityOptions:Password:RequiredUniqueChars", options.Password.RequiredUniqueChars.ToString())); + options.Password.RequireDigit = bool.Parse(sitesettings.GetValue("IdentityOptions:Password:RequireDigit", options.Password.RequireDigit.ToString())); + options.Password.RequireUppercase = bool.Parse(sitesettings.GetValue("IdentityOptions:Password:RequireUppercase", options.Password.RequireUppercase.ToString())); + options.Password.RequireLowercase = bool.Parse(sitesettings.GetValue("IdentityOptions:Password:RequireLowercase", options.Password.RequireLowercase.ToString())); + options.Password.RequireNonAlphanumeric = bool.Parse(sitesettings.GetValue("IdentityOptions:Password:RequireNonAlphanumeric", options.Password.RequireNonAlphanumeric.ToString())); // lockout options - options.Lockout.MaxFailedAccessAttempts = int.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:MaxFailedAccessAttempts", options.Lockout.MaxFailedAccessAttempts.ToString())); - options.Lockout.DefaultLockoutTimeSpan = TimeSpan.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:DefaultLockoutTimeSpan", options.Lockout.DefaultLockoutTimeSpan.ToString())); + options.Lockout.MaxFailedAccessAttempts = int.Parse(sitesettings.GetValue("IdentityOptions:Password:MaxFailedAccessAttempts", options.Lockout.MaxFailedAccessAttempts.ToString())); + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.Parse(sitesettings.GetValue("IdentityOptions:Password:DefaultLockoutTimeSpan", options.Lockout.DefaultLockoutTimeSpan.ToString())); }); return builder; diff --git a/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs b/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs index c24f3acd..8f281883 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Oqtane.Infrastructure; @@ -16,10 +17,10 @@ namespace Microsoft.Extensions.DependencyInjection } public OqtaneSiteOptionsBuilder AddSiteOptions( - Action alias) where TOptions : class, new() + Action> action) where TOptions : class, new() { Services.TryAddSingleton, SiteOptionsCache>(); - Services.AddSingleton, SiteOptions> (sp => new SiteOptions(alias)); + Services.AddSingleton, SiteOptions> (sp => new SiteOptions(action)); Services.TryAddTransient, SiteOptionsFactory>(); Services.TryAddScoped>(sp => BuildOptionsManager(sp)); Services.TryAddSingleton>(sp => BuildOptionsManager(sp)); diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs index 89b127fd..540df02a 100644 --- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs @@ -30,18 +30,18 @@ namespace Oqtane.Infrastructure if (alias != null) { - // add site settings to alias + // save alias in HttpContext + context.Items.Add(Constants.HttpContextAliasKey, alias); + + // save site settings in HttpContext var cache = context.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache; - alias.SiteSettings = cache.GetOrCreate("sitesettings:" + alias.SiteKey, entry => + var sitesettings = cache.GetOrCreate(Constants.HttpContextSiteSettingsKey + alias.SiteKey, entry => { var settingRepository = context.RequestServices.GetService(typeof(ISettingRepository)) as ISettingRepository; return settingRepository.GetSettings(EntityNames.Site, alias.SiteId) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); }); - // save alias in HttpContext for server-side usage - context.Items.Add(Constants.HttpContextAliasKey, alias); - // remove site settings so they are not available client-side - alias.SiteSettings = null; + context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings); // rewrite path by removing alias path prefix from api and pages requests (for consistent routing) if (!string.IsNullOrEmpty(alias.Path)) diff --git a/Oqtane.Server/Infrastructure/Options/ISiteOptions.cs b/Oqtane.Server/Infrastructure/Options/ISiteOptions.cs index 158b2002..c05cd9fa 100644 --- a/Oqtane.Server/Infrastructure/Options/ISiteOptions.cs +++ b/Oqtane.Server/Infrastructure/Options/ISiteOptions.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Oqtane.Models; namespace Oqtane.Infrastructure @@ -5,6 +6,6 @@ namespace Oqtane.Infrastructure public interface ISiteOptions where TOptions : class, new() { - void Configure(TOptions options, Alias alias); + void Configure(TOptions options, Alias alias, Dictionary sitesettings); } } diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptions.cs b/Oqtane.Server/Infrastructure/Options/SiteOptions.cs index 77cf68d6..f4f55f77 100644 --- a/Oqtane.Server/Infrastructure/Options/SiteOptions.cs +++ b/Oqtane.Server/Infrastructure/Options/SiteOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Oqtane.Models; namespace Oqtane.Infrastructure @@ -6,16 +7,16 @@ namespace Oqtane.Infrastructure public class SiteOptions : ISiteOptions where TOptions : class, new() { - private readonly Action configureOptions; + private readonly Action> configureOptions; - public SiteOptions(Action configureOptions) + public SiteOptions(Action> configureOptions) { this.configureOptions = configureOptions; } - public void Configure(TOptions options, Alias alias) + public void Configure(TOptions options, Alias alias, Dictionary sitesettings) { - configureOptions(options, alias); + configureOptions(options, alias, sitesettings); } } } diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs b/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs index bbff918b..05236eb7 100644 --- a/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs +++ b/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; +using Oqtane.Extensions; namespace Oqtane.Infrastructure { @@ -9,14 +11,14 @@ namespace Oqtane.Infrastructure private readonly IConfigureOptions[] _configureOptions; private readonly IPostConfigureOptions[] _postConfigureOptions; private readonly ISiteOptions[] _siteOptions; - private readonly IAliasAccessor _aliasAccessor; + private readonly IHttpContextAccessor _accessor; - public SiteOptionsFactory(IEnumerable> configureOptions, IEnumerable> postConfigureOptions, IEnumerable> siteOptions, IAliasAccessor aliasAccessor) + public SiteOptionsFactory(IEnumerable> configureOptions, IEnumerable> postConfigureOptions, IEnumerable> siteOptions, IHttpContextAccessor accessor) { _configureOptions = configureOptions as IConfigureOptions[] ?? new List>(configureOptions).ToArray(); _postConfigureOptions = postConfigureOptions as IPostConfigureOptions[] ?? new List>(postConfigureOptions).ToArray(); _siteOptions = siteOptions as ISiteOptions[] ?? new List>(siteOptions).ToArray(); - _aliasAccessor = aliasAccessor; + _accessor = accessor; } public TOptions Create(string name) @@ -36,11 +38,11 @@ namespace Oqtane.Infrastructure } // override with site specific options - if (_aliasAccessor?.Alias != null) + if (_accessor.HttpContext?.GetAlias() != null && _accessor.HttpContext?.GetSiteSettings() != null) { foreach (var siteOption in _siteOptions) { - siteOption.Configure(options, _aliasAccessor.Alias); + siteOption.Configure(options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings()); } } diff --git a/Oqtane.Server/Pages/External.cshtml.cs b/Oqtane.Server/Pages/External.cshtml.cs index 5a554366..1bd23ac6 100644 --- a/Oqtane.Server/Pages/External.cshtml.cs +++ b/Oqtane.Server/Pages/External.cshtml.cs @@ -14,7 +14,7 @@ namespace Oqtane.Pages returnurl = (returnurl == null) ? "/" : returnurl; returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl; - var providertype = HttpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:ProviderType", ""); + var providertype = HttpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", ""); if (providertype != "") { return new ChallengeResult(providertype, new AuthenticationProperties { RedirectUri = returnurl }); diff --git a/Oqtane.Server/Repository/SettingRepository.cs b/Oqtane.Server/Repository/SettingRepository.cs index 031acf4d..0003e35c 100644 --- a/Oqtane.Server/Repository/SettingRepository.cs +++ b/Oqtane.Server/Repository/SettingRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; +using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Shared; @@ -11,14 +12,14 @@ namespace Oqtane.Repository { private TenantDBContext _tenant; private MasterDBContext _master; - private readonly SiteState _siteState; + private readonly Alias _alias; private readonly IMemoryCache _cache; - public SettingRepository(TenantDBContext tenant, MasterDBContext master, SiteState siteState, IMemoryCache cache) + public SettingRepository(TenantDBContext tenant, MasterDBContext master, ITenantManager tenantManager, IMemoryCache cache) { _tenant = tenant; _master = master; - _siteState = siteState; + _alias = tenantManager.GetAlias(); _cache = cache; } @@ -149,7 +150,7 @@ namespace Oqtane.Repository { if (EntityName == EntityNames.Site) { - _cache.Remove("sitesettings:" + _siteState.Alias.SiteKey); + _cache.Remove(Constants.HttpContextSiteSettingsKey + _alias.SiteKey); } } } diff --git a/Oqtane.Shared/Interfaces/IAlias.cs b/Oqtane.Shared/Interfaces/IAlias.cs deleted file mode 100644 index 43ae54ef..00000000 --- a/Oqtane.Shared/Interfaces/IAlias.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Oqtane.Models -{ - public interface IAlias - { - int AliasId { get; set; } - string Name { get; set; } - int TenantId { get; set; } - int SiteId { get; set; } - bool IsDefault { get; set; } - string CreatedBy { get; set; } - DateTime CreatedOn { get; set; } - string ModifiedBy { get; set; } - DateTime ModifiedOn { get; set; } - string Path { get; } - string SiteKey { get; } - Dictionary SiteSettings { get; set; } - } -} diff --git a/Oqtane.Shared/Models/Alias.cs b/Oqtane.Shared/Models/Alias.cs index ee3c1727..70e1c886 100644 --- a/Oqtane.Shared/Models/Alias.cs +++ b/Oqtane.Shared/Models/Alias.cs @@ -7,7 +7,7 @@ namespace Oqtane.Models /// /// An Alias maps a url like `oqtane.my` or `oqtane.my/products` to a and /// - public class Alias : IAlias, IAuditable + public class Alias : IAuditable { /// /// The primary ID for internal use. It's also used in API calls to identify the site. @@ -84,7 +84,7 @@ namespace Oqtane.Models /// /// Site-specific settings (only available on the server via HttpContext for security reasons) /// - [NotMapped] - public Dictionary SiteSettings { get; set; } + //[NotMapped] + //public Dictionary SiteSettings { get; set; } } } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index e29115da..f71849d1 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -87,7 +87,7 @@ namespace Oqtane.Shared public static readonly string DefaultVisitorFilter = "bot,crawler,slurp,spider,(none),??"; - public static readonly string HttpContextAliasKey = "SiteState.Alias"; - public static readonly string SiteToken = "{SiteToken}"; + public static readonly string HttpContextAliasKey = "Alias"; + public static readonly string HttpContextSiteSettingsKey = "SiteSettings"; } }