From c9e69776f7f51edc2985eabe3a808d061249859d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 15 May 2019 10:12:28 +0200 Subject: [PATCH 001/202] Minor improvement --- .../SmartStore.Web/Scripts/public.offcanvas-menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js b/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js index c49150c27d..a2eb43f68e 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js @@ -305,7 +305,7 @@ var AjaxMenu = (function ($, window, document, undefined) { initMenu: function () { var nav = $(".megamenu .navbar-nav"); - selectedEntityId = nav.data("selected-entity-id"); + selectedEntityId = nav.data("selected-entity-id") || 0; currentCategoryId = nav.data("current-category-id"); currentProductId = nav.data("current-product-id"); currentManufacturerId = nav.data("current-manufacturer-id"); From aa1af5e9f5870c0256b1ecf832cf9ebe1bfe3b10 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 15 May 2019 10:46:13 +0200 Subject: [PATCH 002/202] Fixes search engine issue (itemtype, "offers" required) on product detail page --- .../SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml index c25bbbdd9d..dad2a525ec 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Partials/Product.Offer.cshtml @@ -4,7 +4,7 @@ @using SmartStore.Core.Domain.Catalog; @using SmartStore.Web; -
+
@if (!Model.AddToCart.DisableBuyButton || !Model.AddToCart.DisableWishlistButton || Model.ProductPrice.ShowLoginNote) From 6c420309a353ea6137194ff43f8789dadf52471a Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 14 May 2019 19:14:53 +0200 Subject: [PATCH 003/202] Set IsFullyInitialized in Application_Start, not on first request --- .../SmartStore.Data/ObjectContextBase.SaveChanges.cs | 3 --- .../Tasks/InitializeSchedulerFilter.cs | 10 +--------- src/Presentation/SmartStore.Web/Global.asax.cs | 3 +++ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs index d3658af812..3faed3ad16 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Validation; using System.Linq; @@ -9,9 +8,7 @@ using System.Threading; using System.Threading.Tasks; using SmartStore.Core; -using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Infrastructure; using SmartStore.Utilities; using EfState = System.Data.Entity.EntityState; diff --git a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs index 8a92d0abe2..665fca2d30 100644 --- a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs +++ b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs @@ -32,15 +32,7 @@ public void OnAuthentication(AuthenticationContext filterContext) var eventPublisher = EngineContext.Current.Resolve(); var logger = EngineContext.Current.Resolve().CreateLogger(); - - // The very first request must set app state to 'fully initialized' - if (!EngineContext.Current.IsFullyInitialized) - { - EngineContext.Current.IsFullyInitialized = true; - eventPublisher.Publish(new AppStartedEvent { HttpContext = filterContext.HttpContext }); - } - - ITaskScheduler taskScheduler = EngineContext.Current.Resolve(); + var taskScheduler = EngineContext.Current.Resolve(); try { diff --git a/src/Presentation/SmartStore.Web/Global.asax.cs b/src/Presentation/SmartStore.Web/Global.asax.cs index 774b34ad3d..cd5008bab6 100644 --- a/src/Presentation/SmartStore.Web/Global.asax.cs +++ b/src/Presentation/SmartStore.Web/Global.asax.cs @@ -158,6 +158,9 @@ protected void Application_Start() // register AutoMapper class maps RegisterClassMaps(engine); + + // Set app state to 'fully initialized' + EngineContext.Current.IsFullyInitialized = true; } else { From e69c7b739711b8a528e290c1a94522cc67d8b1d2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 15 May 2019 23:48:10 +0200 Subject: [PATCH 004/202] Allow SyncedCollection to make lockfree reads --- .../Collections/SyncedCollection.cs | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Collections/SyncedCollection.cs b/src/Libraries/SmartStore.Core/Collections/SyncedCollection.cs index 089d9090e4..d8100effd8 100644 --- a/src/Libraries/SmartStore.Core/Collections/SyncedCollection.cs +++ b/src/Libraries/SmartStore.Core/Collections/SyncedCollection.cs @@ -26,6 +26,8 @@ public SyncedCollection(ICollection wrappedCollection, object syncRoot) public object SyncRoot { get; } + public bool ReadLockFree { get; set; } + public void AddRange(IEnumerable collection) { lock (SyncRoot) @@ -112,10 +114,17 @@ public T this[int index] { get { - lock (SyncRoot) + if (ReadLockFree) { return _col.ElementAt(index); } + else + { + lock (SyncRoot) + { + return _col.ElementAt(index); + } + } } } @@ -125,10 +134,17 @@ public int Count { get { - lock (SyncRoot) + if (ReadLockFree) { return _col.Count(); } + else + { + lock (SyncRoot) + { + return _col.Count(); + } + } } } @@ -152,10 +168,17 @@ public void Clear() public bool Contains(T item) { - lock (SyncRoot) + if (ReadLockFree) { return _col.Contains(item); } + else + { + lock (SyncRoot) + { + return _col.Contains(item); + } + } } public void CopyTo(T[] array, int arrayIndex) @@ -174,17 +197,24 @@ public bool Remove(T item) } } + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + public IEnumerator GetEnumerator() { - lock (SyncRoot) + if (ReadLockFree) { return _col.GetEnumerator(); } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _col.GetEnumerator(); + else + { + lock (SyncRoot) + { + return _col.GetEnumerator(); + } + } } #endregion From 13d917d79607bba7f15649199846ffb7e415c57c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 15 May 2019 23:50:03 +0200 Subject: [PATCH 005/202] (Breaking change) removed HttpModuleInitializedEvent and replaced it with SmartUrlRoutingModule.RegisterAction(). Resolving dependencies during app init stage was not a good idea. --- .../SmartUrlRoutingModule.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs b/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs index 26e9c264c2..3860b7dcd7 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs +++ b/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs @@ -14,19 +14,10 @@ using SmartStore.Core.Infrastructure; using SmartStore.Core.Events; using SmartStore.Core.Data; +using SmartStore.Collections; namespace SmartStore.Web.Framework { - public class HttpModuleInitializedEvent - { - public HttpModuleInitializedEvent(HttpApplication application) - { - Application = application; - } - - public HttpApplication Application { get; private set; } - } - /// /// Request event sequence: /// - BeginRequest @@ -55,7 +46,12 @@ public HttpModuleInitializedEvent(HttpApplication application) public class SmartUrlRoutingModule : IHttpModule { private static readonly object _contextKey = new object(); - private static readonly ConcurrentBag _routes = new ConcurrentBag(); + + private static readonly ICollection> _actions = + new SyncedCollection>(new List>()) { ReadLockFree = true }; + + private static readonly ICollection _routes = + new SyncedCollection(new List()) { ReadLockFree = true }; static SmartUrlRoutingModule() { @@ -93,14 +89,28 @@ public void Init(HttpApplication application) application.PostAuthorizeRequest += (s, e) => PostAuthorizeRequest(new HttpContextWrapper(((HttpApplication)s).Context)); application.PreSendRequestHeaders += (s, e) => PreSendRequestHeaders(new HttpContextWrapper(((HttpApplication)s).Context)); } - + application.PostResolveRequestCache += (s, e) => PostResolveRequestCache(new HttpContextWrapper(((HttpApplication)s).Context)); // Publish event to give plugins the chance to register custom event handlers for the request lifecycle. - EngineContext.Current.Resolve().Publish(new HttpModuleInitializedEvent(application)); + foreach (var action in _actions) + { + action(application); + } } } + /// + /// Registers an action that is called on application init. Call this to register HTTP request lifecycle callbacks / event handlers. + /// + /// Action + public static void RegisterAction(Action action) + { + Guard.NotNull(action, nameof(action)); + + _actions.Add(action); + } + /// /// Registers a path pattern which should be handled by the /// From 43718581f13d67dfab28a116e92cab0ff9917205 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 15 May 2019 23:53:07 +0200 Subject: [PATCH 006/202] Added extension method "HttpContext(Base).SafeGetHttpRequest()" --- .../Extensions/HttpExtensions.cs | 42 +++++++++++++++++++ src/Libraries/SmartStore.Core/WebHelper.cs | 28 ++++--------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs index 0b4f39ebd5..59851619db 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs @@ -11,6 +11,7 @@ using SmartStore.Core.Infrastructure; using SmartStore.Core; using System.Web.Mvc; +using SmartStore.Core.Fakes; namespace SmartStore { @@ -28,6 +29,47 @@ public static class HttpExtensions new Tuple("X-Url-Scheme", "https") }; + /// + /// Tries to get the instance without throwing exceptions + /// + /// The instance or null. + public static HttpRequestBase SafeGetHttpRequest(this HttpContext httpContext) + { + if (httpContext == null) + { + return null; + } + + return SafeGetHttpRequest(new HttpContextWrapper(httpContext)); + } + + /// + /// Tries to get the instance without throwing exceptions + /// + /// The instance or null. + public static HttpRequestBase SafeGetHttpRequest(this HttpContextBase httpContext) + { + if (httpContext == null) + { + return null; + } + + if (httpContext.Handler != null || httpContext is FakeHttpContext) + { + // Having a handler means we're most likely in the MVC routing pipeline. + return httpContext.Request; + } + + try + { + return httpContext.Request; + } + catch + { + return null; + } + } + /// /// Returns wether the specified url is local to the host or not /// diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index 5d501165ca..aff6e9b188 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -45,27 +45,15 @@ public WebHelper(HttpContextBase httpContext) _httpContext = httpContext; } - private HttpRequestBase GetHttpRequest() - { - try - { - return _httpContext.Request; - } - catch - { - return null; - } - } - public virtual string GetUrlReferrer() { - return GetHttpRequest()?.UrlReferrer?.ToString() ?? string.Empty; + return _httpContext.SafeGetHttpRequest()?.UrlReferrer?.ToString() ?? string.Empty; } public virtual string GetClientIdent() { var ipAddress = this.GetCurrentIpAddress(); - var userAgent = GetHttpRequest()?.UserAgent.EmptyNull(); + var userAgent = _httpContext.SafeGetHttpRequest()?.UserAgent.EmptyNull(); if (ipAddress.HasValue() && userAgent.HasValue()) { @@ -82,7 +70,7 @@ public virtual string GetCurrentIpAddress() return _ipAddress; } - var httpRequest = GetHttpRequest(); + var httpRequest = _httpContext.SafeGetHttpRequest(); if (httpRequest == null) { return string.Empty; @@ -139,7 +127,7 @@ public virtual string GetThisPageUrl(bool includeQueryString) public virtual string GetThisPageUrl(bool includeQueryString, bool useSsl) { string url = string.Empty; - var httpRequest = GetHttpRequest(); + var httpRequest = _httpContext.SafeGetHttpRequest(); if (httpRequest == null) return url; @@ -181,7 +169,7 @@ public virtual bool IsCurrentConnectionSecured() if (!_isCurrentConnectionSecured.HasValue) { _isCurrentConnectionSecured = false; - var httpRequest = GetHttpRequest(); + var httpRequest = _httpContext.SafeGetHttpRequest(); if (httpRequest != null) { _isCurrentConnectionSecured = httpRequest.IsSecureConnection(); @@ -193,7 +181,7 @@ public virtual bool IsCurrentConnectionSecured() public virtual string ServerVariables(string name) { - return GetHttpRequest()?.ServerVariables[name].EmptyNull(); + return _httpContext.SafeGetHttpRequest()?.ServerVariables[name].EmptyNull(); } [SuppressMessage("ReSharper", "UnusedMember.Local")] @@ -328,7 +316,7 @@ public virtual string GetStoreLocation(bool useSsl) result = result.Substring(0, result.Length - 1); } - var httpRequest = GetHttpRequest(); + var httpRequest = _httpContext.SafeGetHttpRequest(); if (httpRequest != null) { @@ -421,7 +409,7 @@ public virtual string RemoveQueryString(string url, string queryString) public virtual T QueryString(string name) { - var queryParam = GetHttpRequest()?.QueryString[name]; + var queryParam = _httpContext.SafeGetHttpRequest()?.QueryString[name]; if (!string.IsNullOrEmpty(queryParam)) { From 5b3212d612207c2baacb5f7e97d7a8b5a2204c94 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 15 May 2019 23:53:47 +0200 Subject: [PATCH 007/202] Various minor fixes and typo --- .../Data/Hooks/DefaultDbHookHandler.cs | 4 +--- .../DefaultLifetimeScopeAccessor.cs | 1 + .../Logging/log4net/Log4netLogger.cs | 17 ++++++----------- .../Themes/DefaultThemeRegistry.cs | 9 +++++---- .../Caching/DbCacheExtensions.cs | 2 +- .../Events/ConsumerResolver.cs | 4 +--- .../DependencyRegistrar.cs | 15 +++------------ .../Extensions/HttpExtensions.cs | 10 ++++------ .../Controllers/MediaController.cs | 6 +++--- src/Presentation/SmartStore.Web/Global.asax | 2 +- .../Installation/InstallDataSeeder.cs | 2 +- 11 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs b/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs index 0700430685..39126f48f8 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs @@ -8,7 +8,6 @@ namespace SmartStore.Core.Data.Hooks { public class DefaultDbHookHandler : IDbHookHandler { - private readonly IEnumerable> _hooks; private readonly IList> _saveHooks; private readonly Multimap _hooksRequestCache = new Multimap(); @@ -27,7 +26,6 @@ public class DefaultDbHookHandler : IDbHookHandler public DefaultDbHookHandler(IEnumerable> hooks) { - _hooks = hooks; _saveHooks = hooks.Where(x => x.Metadata.IsLoadHook == false).ToList(); } @@ -35,7 +33,7 @@ public ILogger Logger { get; set; - } + } = NullLogger.Instance; public bool HasImportantSaveHooks() { diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs index 703a26fd2a..1c7fed864c 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs @@ -61,6 +61,7 @@ public ILifetimeScope GetLifetimeScope(Action configurationAct _state.SetState((scope = BeginLifetimeScope(configurationAction))); //scope.CurrentScopeEnding += OnScopeEnding; } + return scope; } diff --git a/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs index 3a251a58d3..6bd1f49e02 100644 --- a/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs @@ -74,17 +74,12 @@ public void Log(LogLevel level, Exception exception, string message, object[] ar protected internal void TryAddExtendedThreadInfo(LoggingEvent loggingEvent) { - HttpRequest httpRequest = null; - bool httpRequestReady = HttpContext.Current?.Handler != null; + var isAppInitialized = EngineContext.Current.IsFullyInitialized; - try - { - if (httpRequestReady) - { - httpRequest = HttpContext.Current.Request; - } - } - catch + // Don't knowingly run into exception + var httpRequest = isAppInitialized ? HttpContext.Current.SafeGetHttpRequest() : null; + + if (httpRequest == null) { loggingEvent.Properties["CustomerId"] = DBNull.Value; loggingEvent.Properties["Url"] = DBNull.Value; @@ -104,7 +99,7 @@ protected internal void TryAddExtendedThreadInfo(LoggingEvent loggingEvent) { using (new ActionDisposable(() => props["sm:ThreadInfoAdded"] = true)) { - if (DataSettings.DatabaseIsInstalled() && EngineContext.Current.IsFullyInitialized) + if (DataSettings.DatabaseIsInstalled() && isAppInitialized) { var container = EngineContext.Current.ContainerManager; diff --git a/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs b/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs index 5a4a2253b2..0c90853524 100644 --- a/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs +++ b/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs @@ -8,6 +8,7 @@ using System.Threading; using SmartStore.Collections; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Logging; using SmartStore.Utilities; @@ -29,10 +30,10 @@ public partial class DefaultThemeRegistry : DisposableObject, IThemeRegistry public DefaultThemeRegistry(IEventPublisher eventPublisher, IApplicationEnvironment env, bool? enableMonitoring, string themesBasePath, bool autoLoadThemes) { - this._enableMonitoring = enableMonitoring ?? CommonHelper.GetAppSetting("sm:MonitorThemesFolder", true); - this._eventPublisher = eventPublisher; - this._env = env; - this._themesBasePath = themesBasePath.NullEmpty() ?? _env.ThemesFolder.RootPath; + _enableMonitoring = enableMonitoring ?? CommonHelper.GetAppSetting("sm:MonitorThemesFolder", true); + _eventPublisher = eventPublisher; + _env = env; + _themesBasePath = themesBasePath.NullEmpty() ?? _env.ThemesFolder.RootPath; Logger = NullLogger.Instance; diff --git a/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs b/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs index cdb68ddb14..e055db6a8c 100644 --- a/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs +++ b/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs @@ -115,7 +115,7 @@ internal static DbCacheEntry GetCacheEntry(this IQueryable source, string { return null; } - + if (!cache.RequestTryGet(cacheKey.Key, out var entry)) { entry = cache.RequestPut(cacheKey.Key, valueFactory(), cacheKey.AffectedEntitySets); diff --git a/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs b/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs index 88742e689c..ebab9eff71 100644 --- a/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs +++ b/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs @@ -41,12 +41,10 @@ public virtual object ResolveParameter(ParameterInfo p, IComponentContext c = nu private bool IsActiveForStore(PluginDescriptor plugin) { - var storeContext = EngineContext.Current.Resolve(); - int storeId = 0; if (EngineContext.Current.IsFullyInitialized) { - storeId = storeContext.CurrentStore.Id; + storeId = _container.Value.Resolve().CurrentStore.Id; } if (storeId == 0) diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 61da50b412..81a35f9a67 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Web; @@ -610,10 +611,10 @@ protected override void Load(ContainerBuilder builder) { builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); var consumerTypes = _typeFinder.FindClassesOfType(typeof(IConsumer)); foreach (var type in consumerTypes) @@ -706,8 +707,6 @@ static HttpContextBase HttpContextBaseFactory(IComponentContext ctx) return new HttpContextWrapper(HttpContext.Current); } - // TODO: determine store url - // register FakeHttpContext when HttpContext is not available return new FakeHttpContext("~/"); } @@ -715,19 +714,11 @@ static HttpContextBase HttpContextBaseFactory(IComponentContext ctx) static bool IsRequestValid() { if (HttpContext.Current == null) - return false; - - try - { - // The "Request" property throws at application startup on IIS integrated pipeline mode - var req = HttpContext.Current.Request; - } - catch { return false; } - return true; + return HttpContext.Current.SafeGetHttpRequest() != null; } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs index 63eecdab18..56dba19bee 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs @@ -215,15 +215,13 @@ internal static void SetUserThemeChoiceInCookie(this HttpContextBase context, st internal static HttpCookie GetPreviewModeCookie(this HttpContextBase context, bool createIfMissing) { - if (context?.Handler == null) - return null; - - var cookie = context.Request?.Cookies?.Get("sm.PreviewModeOverrides"); + var httpRequest = context.SafeGetHttpRequest(); + var cookie = httpRequest?.Cookies?.Get("sm.PreviewModeOverrides"); - if (cookie == null && createIfMissing) + if (cookie == null && createIfMissing && httpRequest != null) { cookie = new HttpCookie("sm.PreviewModeOverrides") { HttpOnly = true }; - context.Request.Cookies.Set(cookie); + httpRequest.Cookies.Set(cookie); } if (cookie != null) diff --git a/src/Presentation/SmartStore.Web/Controllers/MediaController.cs b/src/Presentation/SmartStore.Web/Controllers/MediaController.cs index 664120bbd3..334006f457 100644 --- a/src/Presentation/SmartStore.Web/Controllers/MediaController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/MediaController.cs @@ -126,7 +126,7 @@ public async Task Image(int id /* pictureId*/, string name) var query = CreateImageQuery(mime, extension); var cachedImage = _imageCache.Get(id, nameWithoutExtension, extension, query); - return await HandleImage( + return await HandleImageAsync( query, cachedImage, nameWithoutExtension, @@ -223,7 +223,7 @@ public async Task File(string path) if (isProcessableImage) { var cachedImage = _imageCache.Get(file, query); - return await HandleImage( + return await HandleImageAsync( query, cachedImage, nameWithoutExtension, @@ -282,7 +282,7 @@ async Task getSourceBuffer(string prevMime) } [NonAction] - private async Task HandleImage( + private async Task HandleImageAsync( ProcessImageQuery query, CachedImageResult cachedImage, string nameWithoutExtension, diff --git a/src/Presentation/SmartStore.Web/Global.asax b/src/Presentation/SmartStore.Web/Global.asax index ebaeaa3f40..612debf2a2 100644 --- a/src/Presentation/SmartStore.Web/Global.asax +++ b/src/Presentation/SmartStore.Web/Global.asax @@ -1 +1 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="SmartStore.Web.MvcApplication" Language="C#" %> +<%@ Application Codebehind="Global.asax.cs" Inherits="SmartStore.Web.MvcApplication" Language="C#" %> diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index 017575fd79..89ba47882c 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -488,7 +488,7 @@ protected ILocalizationService LocalizationService var storeMappingService = new StoreMappingService(NullCache.Instance, null, null, null); var storeService = new StoreService(new EfRepository(_ctx)); - var storeContext = new WebStoreContext(new Work(x => storeService)); + var storeContext = new WebStoreContext(storeService, null); var locSettings = new LocalizationSettings(); From ce4cf1c1ec3f767fbd938adad0044ebf465e9da3 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 16 May 2019 02:32:59 +0200 Subject: [PATCH 008/202] (Perf) better CurrentStore resolution strategy with caching to reduce db roundtrips --- .../Stores/StoreService.cs | 22 +- .../WebStoreContext.cs | 192 +++++++++++------- .../Installation/InstallDataSeeder.cs | 7 +- 3 files changed, 130 insertions(+), 91 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Stores/StoreService.cs b/src/Libraries/SmartStore.Services/Stores/StoreService.cs index 68a7280116..f8cde15ab0 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreService.cs @@ -18,17 +18,6 @@ public StoreService(IRepository storeRepository) _storeRepository = storeRepository; } - public virtual void DeleteStore(Store store) - { - Guard.NotNull(store, nameof(store)); - - var allStores = GetAllStores(); - if (allStores.Count == 1) - throw new Exception("You cannot delete the only configured store."); - - _storeRepository.Delete(store); - } - public virtual IList GetAllStores() { var query = _storeRepository.Table @@ -63,6 +52,17 @@ public virtual void UpdateStore(Store store) _storeRepository.Update(store); } + public virtual void DeleteStore(Store store) + { + Guard.NotNull(store, nameof(store)); + + var allStores = GetAllStores(); + if (allStores.Count == 1) + throw new Exception("You cannot delete the only configured store."); + + _storeRepository.Delete(store); + } + public virtual bool IsSingleStoreMode() { if (!_isSingleStoreMode.HasValue) diff --git a/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs b/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs index 855a89a6e4..2a02044a38 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs @@ -1,39 +1,72 @@ using System; using System.Linq; +using System.Collections.Generic; using System.Web; using SmartStore.Core; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Infrastructure.DependencyManagement; -using SmartStore.Services.Stores; -using SmartStore.Core.Fakes; +using System.Collections.Concurrent; +using SmartStore.Core.Data; +using SmartStore.Core.Caching; +using SmartStore.Core.Data.Hooks; namespace SmartStore.Web.Framework { - /// - /// Store context for web application - /// - public partial class WebStoreContext : IStoreContext + public partial class WebStoreContext : DbSaveHook, IStoreContext { + public class StoreEntityCache + { + public IDictionary Stores { get; set; } + public IDictionary HostMap { get; set; } + public int PrimaryStoreId { get; set; } + + public Store GetPrimaryStore() + { + return Stores.Get(PrimaryStoreId); + } + + public Store GetStoreById(int id) + { + return Stores.Get(id); + } + + public Store GetStoreByHostName(string host) + { + if (!string.IsNullOrEmpty(host) && HostMap.TryGetValue(host, out var id)) + { + return Stores.Get(id); + } + + return null; + } + } + internal const string OverriddenStoreIdKey = "OverriddenStoreId"; + const string CacheKey = "stores:all"; - private readonly Work _storeService; - private readonly IWebHelper _webHelper; + private readonly IRepository _rs; private readonly HttpContextBase _httpContext; + private readonly ICacheManager _cache; private Store _currentStore; - public WebStoreContext(Work storeService) + public WebStoreContext(IRepository rs, HttpContextBase httpContext, ICacheManager cache) { - _storeService = storeService; - _httpContext = HttpContext.Current != null ? (HttpContextBase)new HttpContextWrapper(HttpContext.Current) : new FakeHttpContext("~/"); - _webHelper = new WebHelper(_httpContext); + _rs = rs; + _httpContext = httpContext; + _cache = cache; + } + + public int? GetRequestStore() + { + return _httpContext.SafeGetHttpRequest()?.RequestContext?.RouteData?.DataTokens?.Get(OverriddenStoreIdKey)?.Convert(); } public void SetRequestStore(int? storeId) { - try + var dataTokens = _httpContext.SafeGetHttpRequest()?.RequestContext?.RouteData?.DataTokens; + + if (dataTokens != null) { - var dataTokens = _httpContext.Request.RequestContext.RouteData.DataTokens; if (storeId.GetValueOrDefault() > 0) { dataTokens[OverriddenStoreIdKey] = storeId.Value; @@ -45,61 +78,27 @@ public void SetRequestStore(int? storeId) _currentStore = null; } - catch { } } - public int? GetRequestStore() + public int? GetPreviewStore() { - try + var cookie = _httpContext.GetPreviewModeCookie(false); + if (cookie != null) { - // Reduce thrown exceptions in console - if (_httpContext.Handler == null) - return null; - - var value = _httpContext.Request?.RequestContext?.RouteData?.DataTokens?.Get(OverriddenStoreIdKey); - if (value != null) + var value = cookie.Values[OverriddenStoreIdKey]; + if (value.HasValue()) { - return (int)value; + return value.ToInt(); } - - return null; } - catch - { - return null; - } - } - public void SetPreviewStore(int? storeId) - { - try - { - _httpContext.SetPreviewModeValue(OverriddenStoreIdKey, storeId.HasValue ? storeId.Value.ToString() : null); - _currentStore = null; - } - catch { } + return null; } - public int? GetPreviewStore() + public void SetPreviewStore(int? storeId) { - try - { - var cookie = _httpContext.GetPreviewModeCookie(false); - if (cookie != null) - { - var value = cookie.Values[OverriddenStoreIdKey]; - if (value.HasValue()) - { - return value.ToInt(); - } - } - - return null; - } - catch - { - return null; - } + _httpContext.SetPreviewModeValue(OverriddenStoreIdKey, storeId.HasValue ? storeId.Value.ToString() : null); + _currentStore = null; } /// @@ -111,28 +110,27 @@ public Store CurrentStore { if (_currentStore == null) { + var cachedStores = GetCachedStores(); + int? storeOverride = GetRequestStore() ?? GetPreviewStore(); if (storeOverride.HasValue) { - // the store to be used can be overwritten on request basis (e.g. for theme preview, editing etc.) - _currentStore = _storeService.Value.GetStoreById(storeOverride.Value); + // The store to be used can be overwritten on request basis (e.g. for theme preview, editing etc.) + _currentStore = cachedStores.GetStoreById(storeOverride.Value); } if (_currentStore == null) { - // ty to determine the current store by HTTP_HOST - var host = _webHelper.ServerVariables("HTTP_HOST"); - - var allStores = _storeService.Value.GetAllStores(); - var store = allStores.FirstOrDefault(s => s.ContainsHostValue(host)); - - if (store == null) - { - // Load the first found store - store = allStores.FirstOrDefault(); - } - - _currentStore = store ?? throw new Exception("No store could be loaded"); + // Try to determine the current store by HTTP_HOST + var hostName = _httpContext.SafeGetHttpRequest()?.ServerVariables["HTTP_HOST"]; + + _currentStore = + // Try to resolve the current store by HTTP_HOST + cachedStores.GetStoreByHostName(hostName) ?? + // Then resolve primary store + cachedStores.GetPrimaryStore() ?? + // No way + throw new Exception("No store could be loaded."); } } @@ -144,16 +142,54 @@ public Store CurrentStore } } - /// - /// IsSingleStoreMode ? 0 : CurrentStore.Id - /// public int CurrentStoreIdIfMultiStoreMode { get { - return _storeService.Value.IsSingleStoreMode() ? 0 : CurrentStore.Id; + return GetCachedStores().Stores.Count <= 1 ? 0 : CurrentStore.Id; } } + protected StoreEntityCache GetCachedStores() + { + return _cache.Get(CacheKey, () => + { + var entry = new StoreEntityCache(); + + var allStores = _rs.TableUntracked + .Expand(x => x.PrimaryStoreCurrency) + .Expand(x => x.PrimaryExchangeRateCurrency) + .OrderBy(x => x.DisplayOrder) + .ThenBy(x => x.Name) + .ToList(); + + // Detach all entities... you never know. + allStores.Each(x => _rs.Context.DetachEntity(x)); + + entry.Stores = allStores.ToDictionary(x => x.Id); + entry.HostMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var store in allStores) + { + var hostValues = store.ParseHostValues(); + foreach (var host in hostValues) + { + entry.HostMap[host] = store.Id; + } + } + + if (allStores.Count > 0) + { + entry.PrimaryStoreId = allStores.FirstOrDefault().Id; + } + + return entry; + }); + } + + public override void OnAfterSave(IHookedEntity entry) + { + _cache.Remove(CacheKey); + } } } diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index 89ba47882c..3f610c9cf1 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -486,9 +486,12 @@ protected ILocalizationService LocalizationService var rsResources = new EfRepository(_ctx); rsResources.AutoCommitEnabled = false; + var rsStore = new EfRepository(_ctx); + rsStore.AutoCommitEnabled = false; + var storeMappingService = new StoreMappingService(NullCache.Instance, null, null, null); - var storeService = new StoreService(new EfRepository(_ctx)); - var storeContext = new WebStoreContext(storeService, null); + var storeService = new StoreService(rsStore); + var storeContext = new WebStoreContext(rsStore, null, NullCache.Instance); var locSettings = new LocalizationSettings(); From ea6c5dcc3241228fc5162671557f420cb822a4fa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 16 May 2019 02:55:10 +0200 Subject: [PATCH 009/202] Made some WebStoreContext dependencies Lazy<> --- .../WebStoreContext.cs | 21 ++++++++++--------- .../Installation/InstallDataSeeder.cs | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs b/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs index 2a02044a38..797098293c 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebStoreContext.cs @@ -8,6 +8,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Caching; using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Infrastructure.DependencyManagement; namespace SmartStore.Web.Framework { @@ -43,13 +44,13 @@ public Store GetStoreByHostName(string host) internal const string OverriddenStoreIdKey = "OverriddenStoreId"; const string CacheKey = "stores:all"; - private readonly IRepository _rs; - private readonly HttpContextBase _httpContext; + private readonly Lazy> _rs; + private readonly Lazy _httpContext; private readonly ICacheManager _cache; private Store _currentStore; - public WebStoreContext(IRepository rs, HttpContextBase httpContext, ICacheManager cache) + public WebStoreContext(Lazy> rs, Lazy httpContext, ICacheManager cache) { _rs = rs; _httpContext = httpContext; @@ -58,12 +59,12 @@ public WebStoreContext(IRepository rs, HttpContextBase httpContext, ICach public int? GetRequestStore() { - return _httpContext.SafeGetHttpRequest()?.RequestContext?.RouteData?.DataTokens?.Get(OverriddenStoreIdKey)?.Convert(); + return _httpContext.Value.SafeGetHttpRequest()?.RequestContext?.RouteData?.DataTokens?.Get(OverriddenStoreIdKey)?.Convert(); } public void SetRequestStore(int? storeId) { - var dataTokens = _httpContext.SafeGetHttpRequest()?.RequestContext?.RouteData?.DataTokens; + var dataTokens = _httpContext.Value.SafeGetHttpRequest()?.RequestContext?.RouteData?.DataTokens; if (dataTokens != null) { @@ -82,7 +83,7 @@ public void SetRequestStore(int? storeId) public int? GetPreviewStore() { - var cookie = _httpContext.GetPreviewModeCookie(false); + var cookie = _httpContext.Value.GetPreviewModeCookie(false); if (cookie != null) { var value = cookie.Values[OverriddenStoreIdKey]; @@ -97,7 +98,7 @@ public void SetRequestStore(int? storeId) public void SetPreviewStore(int? storeId) { - _httpContext.SetPreviewModeValue(OverriddenStoreIdKey, storeId.HasValue ? storeId.Value.ToString() : null); + _httpContext.Value.SetPreviewModeValue(OverriddenStoreIdKey, storeId.HasValue ? storeId.Value.ToString() : null); _currentStore = null; } @@ -122,7 +123,7 @@ public Store CurrentStore if (_currentStore == null) { // Try to determine the current store by HTTP_HOST - var hostName = _httpContext.SafeGetHttpRequest()?.ServerVariables["HTTP_HOST"]; + var hostName = _httpContext.Value.SafeGetHttpRequest()?.ServerVariables["HTTP_HOST"]; _currentStore = // Try to resolve the current store by HTTP_HOST @@ -156,7 +157,7 @@ protected StoreEntityCache GetCachedStores() { var entry = new StoreEntityCache(); - var allStores = _rs.TableUntracked + var allStores = _rs.Value.TableUntracked .Expand(x => x.PrimaryStoreCurrency) .Expand(x => x.PrimaryExchangeRateCurrency) .OrderBy(x => x.DisplayOrder) @@ -164,7 +165,7 @@ protected StoreEntityCache GetCachedStores() .ToList(); // Detach all entities... you never know. - allStores.Each(x => _rs.Context.DetachEntity(x)); + allStores.Each(x => _rs.Value.Context.DetachEntity(x)); entry.Stores = allStores.ToDictionary(x => x.Id); entry.HostMap = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index 3f610c9cf1..7cfeef0ef3 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -5,6 +5,7 @@ using System.Xml; using SmartStore.Core; using SmartStore.Core.Caching; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Configuration; @@ -491,7 +492,7 @@ protected ILocalizationService LocalizationService var storeMappingService = new StoreMappingService(NullCache.Instance, null, null, null); var storeService = new StoreService(rsStore); - var storeContext = new WebStoreContext(rsStore, null, NullCache.Instance); + var storeContext = new WebStoreContext(new Lazy>(() => rsStore), null, NullCache.Instance); var locSettings = new LocalizationSettings(); From c1f4f7748e97bd803e74ad94964277f824ab2f32 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 16 May 2019 11:29:21 +0200 Subject: [PATCH 010/202] Fixes menu issue "collection was modified, enumeration operation may not execute" --- .../Views/Shared/Menus/Dropdown.cshtml | 7 ++++--- .../Views/Shared/Menus/LinkList.cshtml | 7 ++++--- .../Views/Shared/Menus/ListGroup.cshtml | 5 +++-- .../Views/Shared/Menus/Navbar.cshtml | 17 +++++++++-------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Menus/Dropdown.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Menus/Dropdown.cshtml index b672a1240b..7549e7f479 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Menus/Dropdown.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Menus/Dropdown.cshtml @@ -55,8 +55,6 @@ var itemText = node.GetItemText(T); var itemUrl = item.GenerateUrl(this.ViewContext.RequestContext); - item.LinkHtmlAttributes.PrependCssClass("dropdown-item menu-link"); - if (item.IsGroupHeader) { if (!isFirst) @@ -71,7 +69,10 @@ continue; } - + var attributes = new Dictionary(item.LinkHtmlAttributes); + attributes.PrependCssClass("dropdown-item menu-link"); + + @if (hasIcons) { diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Menus/LinkList.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Menus/LinkList.cshtml index 0d2c754444..ffacbff73f 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Menus/LinkList.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Menus/LinkList.cshtml @@ -26,8 +26,6 @@ var itemText = node.GetItemText(T); var itemUrl = item.GenerateUrl(this.ViewContext.RequestContext); - item.LinkHtmlAttributes.PrependCssClass("menu-link"); - if (item.IsGroupHeader) { if (!isFirst) @@ -42,8 +40,11 @@ continue; } + var attributes = new Dictionary(item.LinkHtmlAttributes); + attributes.PrependCssClass("menu-link"); +
  • - + @if (hasIcons) { diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Menus/ListGroup.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Menus/ListGroup.cshtml index 05ca3ffcd9..a7908c86ab 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Menus/ListGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Menus/ListGroup.cshtml @@ -29,9 +29,10 @@ var itemText = node.GetItemText(T); var itemUrl = item.GenerateUrl(this.ViewContext.RequestContext); - item.LinkHtmlAttributes.PrependCssClass("list-group-item list-group-item-action menu-link" + ((itemState & NodePathState.Selected) == NodePathState.Selected ? " active" : "")); + var attributes = new Dictionary(item.LinkHtmlAttributes); + attributes.PrependCssClass("list-group-item list-group-item-action menu-link" + ((itemState & NodePathState.Selected) == NodePathState.Selected ? " active" : "")); - + @if (hasIcons) { diff --git a/src/Presentation/SmartStore.Web/Views/Shared/Menus/Navbar.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/Menus/Navbar.cshtml index a1114709b8..a803444885 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/Menus/Navbar.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/Menus/Navbar.cshtml @@ -74,13 +74,6 @@ var itemText = node.GetItemText(T); var itemUrl = item.GenerateUrl(this.Url); - item.LinkHtmlAttributes.PrependCssClass("nav-link menu-link" + (isDropDownActive ? " dropdown-toggle" : "")); - if (isDropDownActive) - { - item.LinkHtmlAttributes["aria-expanded"] = "false"; - item.LinkHtmlAttributes["data-target"] = "#dropdown-menu-{0}".FormatInvariant(item.Id); - } - if (item.IsGroupHeader) { if (!isFirst) @@ -95,8 +88,16 @@ continue; } + var attributes = new Dictionary(item.LinkHtmlAttributes); + attributes.PrependCssClass("nav-link menu-link" + (isDropDownActive ? " dropdown-toggle" : "")); + if (isDropDownActive) + { + attributes["aria-expanded"] = "false"; + attributes["data-target"] = "#dropdown-menu-{0}".FormatInvariant(item.Id); + } +
  • - + @Html.SmartLabel(string.Empty, string.Empty)
    - + @Html.SmartLabel(string.Empty, string.Empty)