diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..f731f7194a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,6 @@
+# root = true
+
+# Unix-style newlines with a newline ending every file
+# [*]
+# indent_style = tab
+# indent_size = 4
diff --git a/.gitignore b/.gitignore
index 6679f730aa..85bd82b7ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -278,6 +278,7 @@ $RECYCLE.BIN/
/src/Plugins/*-sym
/src/Tests/*-sym
/src/Presentation/SmartStore.Web/Themes/*-sym
+/src/Presentation/SmartStore.Web/Themes/HP
###########
@@ -299,10 +300,12 @@ Kopie von*
/src/Presentation/SmartStore.Web/App_Data/ImportProfiles/
/src/Presentation/SmartStore.Web/App_Data/Indexing/
/src/Presentation/SmartStore.Web/App_Data/Lucene/
+/src/Presentation/SmartStore.Web/App_Data/EfCache/
/src/Presentation/SmartStore.Web/App_Data/Settings.txt
/src/Presentation/SmartStore.Web/App_Data/InstalledPlugins.txt
/src/Presentation/SmartStore.Web/App_Data/Licenses.lic
/src/Presentation/SmartStore.Web/Media
+/src/**/App_Data/Tenants/
# Import/Export
/src/Presentation/SmartStore.Web/Content/files/[Ee]xport[Ii]mport/*.*
@@ -318,3 +321,4 @@ Kopie von*
src/SmartStoreNET.Packager.sln
Log.txt
+src/Presentation/SmartStore.Web/Themes/FlexMuseo/
diff --git a/CREDITS.txt b/CREDITS.txt
index 88b2d0a6be..5ea4c4a6fb 100644
--- a/CREDITS.txt
+++ b/CREDITS.txt
@@ -1,11 +1,8 @@
SmartStore.NET
-Copyright 1999-2014 SmartStore AG
+Copyright 1999-2018 SmartStore AG
http://www.smartstore.com | https://github.com/smartstoreag/SmartStoreNET
-SmartStore.NET is a fork of the ASP.NET open source e-commerce solution
-nopCommerce (http://www.nopcommerce.com).
-
SmartStore.NET includes works distributed under the licenses listed below.
Please refer to the specific resources for more detailed information about the authors,
copyright notices, and licenses.
@@ -14,7 +11,7 @@ copyright notices, and licenses.
51Degrees
---------------------------------------------
WebSite: http://51degrees.mobi/
-Copyright: Copyright 2010 - 2013 51Degrees.mobi Limited
+Copyright: Copyright 2010 - 2018 51Degrees.mobi Limited
License: Mozilla Public License 2.0 (MPL-2.0)
@@ -28,7 +25,7 @@ License: BSD License
ASP.NET MVC
---------------------------------------------
-Website: http://aspnet.codeplex.com/wikipage?title=MVC&referringTitle=Home
+Website: https://github.com/aspnet/AspNetWebStack
Copyright: Copyright (c) 2008-2011 Microsoft Corporation
License: MICROSOFT ASP.NET MODEL VIEW CONTROLLER 3 EULA
http://go.microsoft.com/fwlink/?LinkID=207621
@@ -36,26 +33,32 @@ License: MICROSOFT ASP.NET MODEL VIEW CONTROLLER 3 EULA
Autofac
---------------------------------------------
-Website: http://code.google.com/p/autofac/
-Copyright: Copyright (c) 2007-2014 Autofac Contributors
- http://code.google.com/p/autofac/wiki/Contributing
+Website: https://autofac.org/
+Copyright: Copyright (c) 2007-2018 Autofac Contributors
License: MIT
AutoMapper
---------------------------------------------
WebSite: http://www.automapper.org
-Copyright: Copyright 2008-2013 Jimmy Bogard and other contributors
+Copyright: Copyright 2008-2018 Jimmy Bogard and other contributors
License: MIT
Bundle Transformer
---------------------------------------------
-Website: https://bundletransformer.codeplex.com
+Website: https://github.com/Taritsyn/BundleTransformer
Copyright: Copyright Andrey Taritsyn 2014
License: Apache License 2.0 (Apache)
+summernote
+---------------------------------------------
+WebSite: https://summernote.org/
+Copyright: Copyright (c) 2015~ Summernote Team (https://github.com/orgs/summernote/people)
+License: MIT
+
+
DotNetOpenAuth
---------------------------------------------
Website: http://dotnetopenauth.net/
@@ -65,34 +68,28 @@ License: MS-PL
DotNetZip
---------------------------------------------
-Website: http://dotnetzip.codeplex.com/
+Website: https://archive.codeplex.com/?p=dotnetzip
Copyright:
License: MS-PL
Entity Framework
---------------------------------------------
-Website: https://entityframework.codeplex.com
-Copyright: Copyright (c) 2012-2014 Microsoft Corporation
+Website: https://github.com/aspnet/EntityFramework6
+Copyright: Copyright (c) 2012-2018 Microsoft Corporation
License: Apache License 2.0 (Apache)
EPPlus
---------------------------------------------
-Website: https://epplus.codeplex.com/
+Website: https://github.com/JanKallman/EPPlus
Copyright: Copyright (C) 2011 Jan Kllman
License: GNU Library General Public License (LGPL)
-Fasterflect
----------------------------------------------
-Website: https://fasterflect.codeplex.com/
-License: Apache License 2.0 (Apache)
-
-
Fluent Validation
---------------------------------------------
-Website: https://fluentvalidation.codeplex.com/
+Website: https://github.com/JeremySkinner/FluentValidation
License: Apache License 2.0 (Apache)
@@ -103,11 +100,11 @@ Copyright: Copyright (C) 2014 Glimpse contributors (http://getglimpse.com/Co
License: Apache License 2.0 (Apache)
-ImageResizer.NET
+ImageProcessor
---------------------------------------------
-Website: https://imageresizing.net/
-Copyright: Copyright (c) 2012 Imazen
-License: Freedom License
+Website: http://imageprocessor.org/
+Copyright: Copyright (c) 2018 James Jackson-South
+License: Apache License 2.0 (Apache)
JavaScriptEngineSwitcher
@@ -131,16 +128,9 @@ Copyright: Copyright (c) 2007-2009 Ariel Flesler
License: MIT
-jQuery UI (Core, Widget, Datepicker)
----------------------------------------------
-Website: http://docs.jquery.com/UI
-Copyright: Copyright (c) 2010 lib/jquery.ui/AUTHORS.txt (http://jqueryui.com/about)
-License: MIT
-
-
-JSON.NET
+Json.NET
---------------------------------------------
-Website: http://james.newtonking.com/
+Website: https://www.newtonsoft.com/json
Copyright: Copyright (c) 2007 James Newton-King
License: MIT
@@ -152,6 +142,13 @@ Copyright: Copyright (c) 2014 Maxmind
License: Creative Commons Attribution-ShareAlike 3.0 Unported License
+Linearicons by Perxis
+---------------------------------------------
+Website: https://linearicons.com
+Copyright: Copyright (c) 2015 Linearicons.com (https://perxis.com/)
+License: Dual (CC BY-SA 4.0 and commercial)
+
+
Microsoft SQL Server Compact 4.0
---------------------------------------------
Website: http://www.microsoft.com
@@ -173,35 +170,49 @@ License: MIT
NuGet
---------------------------------------------
-Website: http://nuget.codeplex.com
-Copyright: Copyright 2010-2011 Outercurve Foundation
+Website: https://github.com/nuget/home
+Copyright: Copyright 2010-2018 Outercurve Foundation
License: Apache Software Foundation License 2.0
nUnit
---------------------------------------------
-Website: http://www.nunit.org/index.php
+Website: http://nunit.org/
Copyright: Copyright (c) 2002-2007 NUnit.org
- Portions Copyright (c) 2002-2008 Charlie Poole or Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright (c) 2000-2002 Philip A. Craig
+ Portions Copyright (c) 2002-2018 Charlie Poole or Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright (c) 2000-2002 Philip A. Craig
License: Derived from zlib: http://nunit.org/index.php?p=license&r=2.4
+PhotoSwipe
+---------------------------------------------
+Website: http://photoswipe.com/
+Copyright: Copyright (c) 2014-2018 Dmitry Semenov, http://dimsemenov.com
+License: MIT
+
+
+slick carousel
+---------------------------------------------
+Website: http://kenwheeler.github.io/slick/
+Copyright: Copyright (c) 2018 Ken Wheeler
+License: MIT
+
+
Telerik MVC Extensions
---------------------------------------------
Website: http://www.telerik.com/
License: GPL v3.0
-TinyMCE
+Twitter Bootstrap
---------------------------------------------
-WebSite: http://tinymce.moxiecode.com/
-Copyright: Copyright (c) 2003-2009 Moxiecode Systems AB
-License: LGPL 2.1
+Website: https://getbootstrap.com/
+License: Apache License v2.0
-Twitter Bootstrap
+ua_parser
---------------------------------------------
-Website: http://twitter.github.io/bootstrap/
+Website: https://github.com/ua-parser/uap-csharp
+Copyright: Copyright 2012 Sren Enemrke
License: Apache License v2.0
diff --git a/README.md b/README.md
index f6fc1133c9..f8e84aa9bb 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,60 @@
-## Overview
+
+
+
+
+
+
+
+
+
+
+
+
+Ready. Sell. Grow.
+
-
+
+
+
+
+
+
-SmartStore.NET is a free, open source, full-featured e-commerce solution for companies of any size. It is web standards compliant and incorporates the newest Microsoft web technology stack.
+
+ Try Online ∙
+ Docs ∙
+ Forum ∙
+ Marketplace ∙
+ Translations
+
+
-**SmartStore.NET includes all essential features to create multilingual and multi-currency stores** targeting desktop or mobile devices and enabling SEO optimized rich product catalogs with support for an unlimited number of products and categories, variants, bundles, datasheets, ESD, discounts, coupons and many more.
+Smartstore.NET is a free, open source, full-featured e-commerce solution for companies of any size. It is web standards compliant and incorporates the newest Microsoft web technology stack.
-A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping handling, etc. makes SmartStore.NET a powerful all-in-one solution fulfilling all your needs... totally FREE!
+**Smartstore.NET includes all essential features to create multilingual and multi-currency stores** targeting desktop or mobile devices and enabling SEO optimized rich product catalogs with support for an unlimited number of products and categories, variants, bundles, datasheets, ESD, discounts, coupons and many more.
-**SmartStore.NET delivers a beautiful and configurable shop front-end out-of-the-box**, built with a design approach on the highest level, including components like `Twitter Bootstrap`, `Less CSS` and others. The innovative ShopBar, a powerful layered navigation, reviews & ratings, an optimized checkout process... they all provide a perfect shopping experience for your buyers.
+A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping handling, etc. makes Smartstore.NET a powerful all-in-one solution fulfilling all your needs.
-The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC 5`, `Entity Framework 6` and Domain Driven Design approach - makes it easy to extend, extremely flexible and essentially fun to work with ;-)
+
+
+
+
+
+
+**Smartstore.NET delivers a beautiful and configurable shop front-end out-of-the-box**, built with a design approach on the highest level, including components like `Bootstrap 4`, `Sass` and others. The supplied theme _Flex_ is modern, clean and fully responsive, giving buyers the best possible shopping experience on any device.
+
+The state-of-the-art architecture of Smartstore.NET - with `ASP.NET 4.5` + `MVC 5`, `Entity Framework 6` and Domain Driven Design approach - makes it easy to extend, extremely flexible and essentially fun to work with ;-)
* **Website:** [http://www.smartstore.com/en/net](http://www.smartstore.com/en/net)
* **Forum:** [http://community.smartstore.com](http://community.smartstore.com)
* **Marketplace:** [http://community.smartstore.com/marketplace](http://community.smartstore.com/marketplace)
-* **Documentation:** [SmartStore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home)
+* **Translations:** [http://translate.smartstore.com/](http://translate.smartstore.com/)
+* **Documentation:** [Smartstore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home)
+
+
+
## Highlights
@@ -25,22 +62,25 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC
* State of the art architecture thanks to `ASP.NET 4.5`, `ASP.NET MVC 5`, `Entity Framework 6` and Domain Driven Design
* Easy to extend and extremely flexible thanks to modular design
-* (NEW) A powerful theming engine lets you create themes & skins with minimum effort thanks to theme inheritance
-* Consistent and sophisticated use of modern components such as `jQuery`, `Twitter Bootstrap`, `LESS` & more in the front and back end.
-* Easy shop management thanks to modern and clean UI
-* Out of the box shop design on the highest level
-* Revolutionary Shopbar for the best shopping experience
+* Highly scalable thanks to full page caching and web farm support
+* A powerful theming engine lets you create themes & skins with minimum effort thanks to theme inheritance
* Point&Click Theme configuration
+* Highly professional search framework based on Lucene.NET, delivering ultra fast faceted search results
+* Consistent and sophisticated use of modern components such as `jQuery`, `Bootstrap 4`, `Sass` & more in the front and back end.
+* Easy shop management thanks to modern and clean UI
### Features
+* NEW: CMS Page Builder
+* NEW: CMS Menu Builder
* Multi-Store support
* Unlimited number of products and categories
* Product Bundles
* RESTful WebApi
-* Multi-language support
-* 100% Mobile optimized
-* Perfectly optimized for search engines
+* Multi-language and RTL support
+* Modern, clean, SEO-optimized and fully responsive Theme based on Bootstrap 4
+* Ultra fast search framework with faceted search support
+* Extremely scalable thanks to output caching, REDIS & Microsoft Azure support.
* Trusted Shops precertification
* 100% compliant with German jurisdiction
* Sales-, Customer- & Inventory-management
@@ -53,42 +93,47 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC
* CMS (Blog, Forum, custom pages & HTML content etc.)
* and many more...
+
+
## Project Status
-SmartStore.NET V2.6.0 has been released on June 23, 2016. The highlights are:
-* Major improvements in Importer & Exporter: better field mapping, higher performance, bug fixes etc.
-* 'PayPal PLUS' payment plugin
-* 'paydirekt' payment plugin
-* 'Viveum' payment plugin
-* 'BeezUP' export provider
-* (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure.
-* Lots of bug fixes
+Smartstore.NET V3.2.0 has been released on 10 May, 2019. The highlights are:
+* (NEW) **Page Builder**: Create fascinating content that will boost your sales. No coding is required thanks to a powerful WYSIWYG editor which utilizes the revolutionary *CSS Grid* system (commercial plugin)
+* (NEW) **Menu Builder**: Visual manager for all sorts of menus. Change existing menus or create your own and place them anywhere you want.
+* (Perf) **Faster MegaSearch** thanks to Lucene.NET 4.8
+* (Perf) **Huge performance increase** in discount resolution and calculation.
-##Try it online
-We have set up a live online demo for you so you are able to test SmartStore.NET without local installation. Get a first impression and test all available features in the front- and in the backend. Please keep in mind that the backend demo is shared and other testers can modify data at the same time.
+## Try it online
+
+We have set up a live online demo for you so you are able to test Smartstore.NET without local installation. Get a first impression and test all available features in the front- and in the backend. Please keep in mind that the backend demo is shared and other testers can modify data at the same time.
* [**Frontend**](http://frontend.smartstore.net/en) (User: demo, PWD: 1234)
* [**Backend**](http://backend.smartstore.net/en/login) (User: demo, PWD: 1234)
-##How to install
+
+
+## How to install
* Download the latest stable release from the download tab and unzip it to your web folder
* Setup a website in IIS and point the file directory to your unzipped folder
* Fire up your browser and follow the installation instructions
* Enjoy ;-)
-###System requirements
+NOTE: Smartstore.NET 3 requires [Visual C++ Redistributable für Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=52685) which is already pre-installed on most systems. If, nevertheless, it is missing on your web server, just download and execute the installer or ask your hosting provider to do that for you.
+
+### System requirements
* IIS 7+
-* ASP.NET 4.5
-* ASP.NET MVC 5
+* ASP.NET 4.5+
* MS SQL Server 2008 Express (or higher) OR MS SQL Server Compact 4
+* Visual C++ Redistributable für Visual Studio 2015 ([Download](https://www.microsoft.com/en-US/download/details.aspx?id=52685))
* Full Trust
+
## License
-SmartStore.NET is released under the [GPLv3 license](http://www.gnu.org/licenses/gpl-3.0.txt).
+Smartstore.NET Community Edition is released under the [GPLv3 license](http://www.gnu.org/licenses/gpl-3.0.txt).
diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets
index f501b36903..080c78d497 100644
--- a/SmartStoreNET.Tasks.Targets
+++ b/SmartStoreNET.Tasks.Targets
@@ -19,7 +19,6 @@
$(RootFolder)\buildtasks
$(RootFolder)\artifacts
$(LibFolder)\sqlce
- $(LibFolder)\ClearScript.V8
$(ArtifactsFolder)\Source
$(ArtifactsFolder)\MsDeploy
$(ArtifactsFolder)\Gallery
@@ -28,13 +27,9 @@
$(WebFolder)\Plugins
$(WebFolder)\Themes
- $(BuildFolder)\Compile
- $(CompileFolder)\_PublishedWebsites
$(BuildFolder)\Web
$(BuildFolder)\MsDeploy
$(BuildFolder)\Precompiled
- $(BuildFolder)\Profiling
- $(BuildFolder)\Gallery
$(WebFolder)\Web.config
$(WebFolder)\Web.$(Configuration).config
@@ -44,11 +39,11 @@
x86
$(BUILD_NUMBER)
- 2.6.0
+ 3.2.2
$(StageFolder)
.$(Version)
- .Web
+ .Community
@@ -95,17 +90,20 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -126,7 +124,8 @@
-
+
+
@@ -137,13 +136,13 @@
Properties="WebProjectOutputDir=$(StageFolder)\Administration\;
OutDir=$(StageFolder)\Administration\bin\;Configuration=$(Configuration)" />
-
-
-
-
+
+
+
+
-
+
@@ -153,17 +152,22 @@
-
+
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
@@ -176,7 +180,19 @@
-
+
+
+
+
+
+
+
+ $(StageFolder)\bin
+
+
+
+
+
diff --git a/assets/SmartStoreNET.png b/assets/SmartStoreNET.png
deleted file mode 100644
index 1fc5ca5409..0000000000
Binary files a/assets/SmartStoreNET.png and /dev/null differ
diff --git a/assets/smartstore-icon.png b/assets/smartstore-icon.png
new file mode 100644
index 0000000000..e6414db195
Binary files /dev/null and b/assets/smartstore-icon.png differ
diff --git a/assets/smartstore-text.png b/assets/smartstore-text.png
new file mode 100644
index 0000000000..25bf9558fb
Binary files /dev/null and b/assets/smartstore-text.png differ
diff --git a/assets/smnet3-devices-sm.png b/assets/smnet3-devices-sm.png
new file mode 100644
index 0000000000..2165a8ca6e
Binary files /dev/null and b/assets/smnet3-devices-sm.png differ
diff --git a/build.bat b/build.bat
index e8c6a8c006..94a6c72519 100644
--- a/build.bat
+++ b/build.bat
@@ -1,42 +1,45 @@
-FOR %%b in (
- "%VS140COMNTOOLS%..\..\VC\vcvarsall.bat"
- "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
- "%ProgramFiles%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
-
- "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat"
- "%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"
- "%ProgramFiles%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"
-
- "%VS110COMNTOOLS%..\..\VC\vcvarsall.bat"
- "%ProgramFiles(x86)%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"
- "%ProgramFiles%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"
- ) do (
- if exist %%b (
- call %%b x86
- goto findmsbuild
- )
-)
-
-echo "Unable to detect suitable environment. Build may not succeed."
-
-:findmsbuild
-
SETLOCAL ENABLEDELAYEDEXPANSION
-FOR %%p in (
- "%ProgramFiles(x86)%\MSBuild\14.0\Bin"
- "%ProgramFiles%\MSBuild\14.0\Bin"
- ) do (
- if exist %%p (
- if not defined MsBuildPath (
- SET "MsBuildPath=%%~p"
- goto build
+for /f "usebackq tokens=1* delims=: " %%i in (`lib\vswhere\vswhere -latest -requires Microsoft.Component.MSBuild`) do (
+ if /i "%%i"=="installationPath" (
+ set InstallDir=%%j
+ echo !InstallDir!
+ if exist "!InstallDir!\MSBuild\15.0\Bin\MSBuild.exe" (
+ echo "Using MSBuild from Visual Studio 2017"
+ set msbuild="!InstallDir!\MSBuild\15.0\Bin\MSBuild.exe"
+ goto build
+ )
+ if exist "!InstallDir!\MSBuild\Current\Bin\MSBuild.exe" (
+ echo "Using MSBuild from Visual Studio 2019"
+ set msbuild="!InstallDir!\MSBuild\Current\Bin\MSBuild.exe"
+ goto build
)
)
)
-echo "Unable to detect suitable MsBuild version (14.0). Build may not succeed."
+FOR %%b in (
+ "%VS150COMNTOOLS%\vsvars32.bat"
+ "%VS140COMNTOOLS%\vsvars32.bat"
+ "%VS120COMNTOOLS%\vsvars32.bat"
+ "%VS110COMNTOOLS%\vsvars32.bat"
+ ) do (
+ if exist %%b (
+ echo "Using MSBuild from %%b"
+ call %%b
+ set msbuild="msbuild"
+ goto build
+ )
+)
+
+echo "Unable to detect suitable environment. Build may not succeed."
:build
-call "!MsBuildPath!\msbuild.exe" SmartStoreNET.proj /p:DebugSymbols=false /p:DebugType=None /P:SlnName=SmartStoreNET /maxcpucount %*
\ No newline at end of file
+cd /d %~dp0
+
+echo "Restoring NuGet packages"
+lib\nuget\nuget.exe restore "src\SmartStoreNET.sln"
+
+%msbuild% SmartStoreNET.proj /p:SlnName=SmartStoreNET /m /p:DebugSymbols=false /p:DebugType=None /maxcpucount %*
+
+:end
diff --git a/changelog.md b/changelog.md
index 0a6f3d094c..81357cca18 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,44 +1,636 @@
-# Release Notes
+# Release Notes
-## SmartStore.NET 3.0
+## SmartStore.NET 3.2.2
+
+### New Features
+* External category links: alternative links for categories in the main menu and in category listings.
+* Page Builder: multistore support for stories.
+* #1639 Shipping.ByTotal: Add option to calculate the shipping rate based on the net total instead of the gross total.
+* #1537 Implement "Ratenzahlung powered by PayPal" provider.
+* New Heidelpay payment plugin.
+* Shop-Connector: mapping of product SKUs that differ between client shops.
+* MegaMenu supports configuration of menu items
+
+
+### Improvements
+* Amazon Pay: PSD2. Strong customer authentication (SCA) upgrade.
+* Updated *MaxMind.GeoIP* library and country database
+* Removed HTML encoding for meta tags and document title.
+* #1662 Quantity Units: added field for name pluralization.
+* (SEO) combined SSL and canonical host 301 redirections to prevent successive redirects.
+* (SEO) Activated GZIP compression for all dynamic and static responses.
+* Set max. file upload size to 2 GB (globally)
+* (Dev) Added a sample block for the *Page Builder* in DevTools plugin.
+* Various other minor improvements.
+* PayPal PLUS: paying by instalments can be selected in payment wall.
+* Payone: send more information to API (e.g. shipping address) to stay as frictionless as possible for PSD2.
+* PageBuilder: Added keyboard shortcuts
+* #1582 Added structured data to reviews
+* BMEcat: added support for import of specification attributes
+
+### Bugfixes
+* Fixed wrong XML sitemap index numbering.
+* #1646 Setting ManufacturerItemsToDisplayOnHomepage to 1 will not show any manufacturer.
+* #1647 All manufacturers page: link of last character missing in alphabetical letter list.
+* #1664 RTL: CMS menu issues in backend.
+* #1677 Page Builder: moving and resizing blocks via drag & drop didn't work correctly in RTL.
+* Do not adjust inventory twice when an order is cancelled and deleted.
+* #1500 Fallback to fixed VAT rate instead of rate by region for countries that are not subject to vat.
+* Fixed "The entity wrapper stored in the proxy does not reference the same proxy" when copying an export profile.
+* Various other minor fixes.
+* BMEcat: set TaxCategoryId & MainPictureId for newly imported products
+
+
+
+## SmartStore.NET 3.2.1
+
+### Improvements
+* Implemented file range support for CachedFileResult
+* Sitemap should only include products visible for current customer, which is the search engine system customer
+* Fixed uniform spelling for query string parameter "returnUrl"
+* Fixed search engine issue (itemtype, "offers" required) on product detail page
+* Summernote: solved html formatting issues thanks to "Beautifier" library
+* Media middleware: simplified ETag generation & handling
+
+### Bugfixes
+
+* Mega Menu: Product rotator wasn't working
+* Tiny Image: Fixed periodic IOException
+* URL Rewriter: performance optimization
+* Fixed more menu for main catalog navigation
+* Fixed DateTime nor expressed as UTC in AzureFile & LocalFile
+* Content Slider: Fixed slide display bug on surface and other touch devices
+* Content Slider: Randomize slides > Value was stored in database but toggle button has always shown "off"
+* Page Builder: Fixed slider bug for list blocks (brand, category, products) in Firefox
+* Page Builder: fixes blurry background images in IOS
+* Output Cache: action widgets didn't render when they were part of a widgetzone within a donut child action
+* Multishop resolution failed under certain circumstances
+* Fixed menu issue "collection was modified, enumeration operation may not execute"
+* Fixed "The parameters dictionary contains a null entry for parameter 'catId' of non-nullable type 'System.Int32'"
+* Fixed discount coupon sometimes wasn't applied
+* Fixed "Value cannot be null. Parameter name: key" in order list
+* Fixed topic link resolving by system name in multistore environment
+* Async event consumers were not guaranteed to be awaited
+
+
+## SmartStore.NET 3.2
+
+### Highlights
+* (NEW) **CMS Page Builder**: Create fascinating content that will boost your sales. No coding is required thanks to a powerful WYSIWYG editor which utilizes the revolutionary *CSS Grid* system (commercial plugin)
+* (NEW) **CMS Menu Builder**: Visual manager for all sorts of menus. Change existing menus or create your own and place them anywhere you want.
+* (Perf) **Faster MegaSearch** thanks to Lucene.NET 4.8
+* (Perf) **Huge performance increase** in discount resolution and calculation.
+
+### New Features
+* **EmailReminder**:
+ * Reminders for open shopping carts
+ * Reminders for product reviews
+ * Reminders to visit the shop after a long absence
+* **Scheduling**
+ * Task execution history
+ * Web farms: tasks can run decidedly on each machine of a web farm
+ * #1144 MegaSearch: multi server search index
+* **Topics & Pages**
+ * Access control lists
+ * Grid paging & filtering
+ * New properties: added **IsPublished**, **Short Title** (link text) and **Intro** (teaser) properties.
+ * #1542: added options to set body CSS class and Html Id.
+* **MegaSearch**:
+ * Supports searching for forum posts.
+ * #1172 Option to display related search terms on search page.
+ * Command to continue a previously aborted search index rebuild.
+* **Import/export of**:
+ * Product tags
+ * Tier prices
+ * Product attribute options
+ * Product attribute combinations (update only)
+* **Forum**:
+ * Added option to display a captcha on forum pages when creating or replying to a topic.
+ * #417 Restrict forum groups to specific customer roles.
+ * Added published property to forum topic and post.
+ * Added voting for forum posts.
+ * Several performance improvements.
+* **BeezUp**:
+ * #1459 Add option to only submit one category name per product
+ * Allow to specify export categories per product
+* **Santander**:
+ * Added payment method for instalment purchase.
+ * Added financing calculator.
+* Updated icon library to **Font Awesome 5 Free** with the possibility to activate pro version in the frontend.
+* New storefront catalog options: **ShowSubCategoriesInSubPages**, **ShowDescriptionInSubPages** & **IncludeFeaturedProductsInSubPages** (Subpage = List index > 1 or any active filter).
+* New security option: Use **invisible reCAPTCHA**
+* Wallet: Allow customer to choose whether refund should be submitted to his wallet.
+* Added option to display preview pictures in product lists
+* Added option to add multiple file versions to product download section
+* Added options for alternating price display (in badges)
+* Customer avatar: Letter with colored background if no avatar image was uploaded.
+* Viveum: Supports payment via "Virtual Account Brands" (e.g. PayPal).
+* Added options for alternating price display (in badges).
+* #1515 Poll: Add result tab with a list of answers and customers for a poll
+* BMEcat: Added export and import of product tags.
+* Web-API: Added endpoints for ProductSpecificationAttribute.
+* Trusted Shops: Added new Trustbadge display for mobile devices
+* (Dev) Added *sm:EfCommandTimeout* setting to web.config (was 30 sec. fixed, now configurable)
+
+### Improvements
+* (Perf) Significantly increased query performance for products with a lot of category assignments (> 10).
+* (Perf) Increased app startup speed by up to 20%.
+* (Perf) Ultra-fast file-based XML sitemap generation for extremely large catalogs (> 1M)
+ * At least 10x faster
+ * Generated files are saved on the hard disk now: a rebuild after an app restart is no longer necessary.
+ * No exclusive locks during rebuilds anymore: if an (outdated) file already exists, it is returned instantly.
+* (Dev) Enhanced pub/sub: new `IConsumer` marker interface for classes that contain one or more event message handlers. The generic `IConsumer` implements `IConsumer` but has been marked as `Obsolete`.
+* Better protection against XSS attacks
+* Updated to Bootstrap version 4.1.3
+* **Debitoor**:
+ * Partially update customer instead of full update to avoid all fields being overwritten.
+ * #1540 Place company name in front of customer name (according to address format of the particular country).
+ * #1565 Add a button to manually create a draft invoice.
+* #1479 Show in messages the delivery time at the time of purchase
+* #1184 Sort current shopping carts & current wishlists by `ShoppingCartItem.CreatedOn`.
+* #1106 BMECat: import & export support for product keywords
+* #1499 Added hint to forms indicating that fields with an asterisk (*) are required
+* Added filter for newsletter subscriber export by working language
+* Refactored download section
+* Enhanced EntityPicker to pick from customers, manufacturers & categories
+* #1510 Breadcrumb of an associated product should include the grouped product if it has no assigned categories.
+* OpenTrans: added customer number to parties
+* Do not filter cookie using resources if cookie usage has not yet been consented to.
+* #1563 QueuedMessagesClearTask: add a setting for the age of the mails to be deleted.
+* #1569 Added a setting to show login note if no prices are displayed due to customer group permissions.
+* PayPal PLUS: Up to 10 more third party payment methods are allowed by PayPal now.
+* #1560 Tell-a-Friend and anonymous user: render alert box and tell guest to login to use this function
+* #1571 Compare products now shows all specification attribute options
+* #1539 Signing in is now allowed with e-mail and username
+* Trusted Shops: Trustbadge won't be displayed in Popups & Iframes anymore
+* #1461 Admin Grid: filter dialog will be displayed entirely even when grid has no data to display
+* Lots of minor enhancements for both frontend and admin theme
+* Many other minor improvements
+
+### Bugfixes
+* In a multi-store environment, multiple topics with the same system name can now be resolved reliably.
+* **GMC**:
+ * Export the product images if no attribute images are defined
+ * Do not export the first image twice for additional images
+ * Export image URL of full size image (not default size) for additional images
+ * Custom labels are now being exported
+* Media middleware: 0-byte files should be treated as missing.
+* Megamenu alpha/omega blends do now toggle correctly on touch devices
+* Summernote HTML editor exceeds parent container width when CodeMirror is activated
+* Only display a zero search hits warning if at least one filter is activated
+* #1436 Do not display delivery time in customer order completed messages
+* "ArgumentNullException: The value must not be NULL" if a topic is password protected
+* Tax by region: Fixes after inserting a tax rate country column shows "Unavailable"
+* #1014 Switching to default language keeps specific URL alias of current page
+* Shipping by total: When inserting a record the country now will be saved
+* #1460 Editing of the customer title was missing on customer and address pages in the backend
+* #1447 Checkout button payment methods (Amazon, PayPal Express) won't work in conjunction with mandatory checkout attributes
+* Fixed Autofac circularity error
+* When creating a topic, the widget zone input has shown System.String[]
+* Switching the language always redirected to the home page if SEO friendly URLs was deactivated.
+* File upload of a checkout attribute was not stored on cart page.
+* Redirecting within checkout may have displayed an incorrect URL in the browser.
+* Server cannot modify cookies after HTTP headers have been sent.
+* Wrong base price on product and cart page when a special price is active.
+* In a multi-store, message templates may have loaded the wrong disclaimer and conditions-of-use text.
+* NullReferenceException in manufacturer list when there is no manufacturer.
+* Wrong order of featured products on category page.
+* #1504 Cart item price calculation was wrong if attribute combinations with text types were involved.
+* #1485 Dropdown list for product sorting didn't not work with Internet Explorer 11.
+* #1468 Twitter authentication wasn't working anymore.
+* Newsletter subscription didn't work when customer privacy setting DisplayGdprConsentOnForms was turned off
+* Fixed social media image detection
+* Fixed redirection of bots when several languages were active
+* Region cannot be selected in checkout when entering a billing or shipping address
+* Fixed invalid conversion of "System.Int32" to "SmartStore.Core.Domain.Tax.VatNumberStatus" when placing an order
+* MegaMenu: Improved item rendering for third tier elements
+* Product display order on category and manufacturer pages was sometimes wrong when using linq search.
+* Debitoor: Wrong invoice total if a subtotal discount has been applied.
+* Import: Fixes invalid conversion "System.Double" to "SmartStore.Core.Domain.Catalog.QuantityControlType".
+* Topics: Fixes "Cannot insert duplicate key row in object 'dbo.UrlRecord' with unique index 'IX_UrlRecord_Slug'".
+* #1566 Santander: eliminate the 1 cent rounding difference at amountTotalNet.
+* Fixed redirection to the homepage for pages which are loaded while the application is restarted.
+* #1570 Filter option "Only deactivated customers" filters deleted instead of deactivated customers.
+* #1475 select boxes must be wrapped on mobile devices if data-select-url is set
+* Fixed the redirection to the homepage for pages which were loaded while the application was restarted
+* Fixes product feeds expect a different base price formatting.
+* #1369 Shopping cart shows "Discount code applied", although it is not applied due to a lower tier price.
+* Many other fixes...
+
+
+## SmartStore.NET 3.1.5
+
+### Highlights
+* Compliance with EU-GDPR requirements
+* Search engine friendly topic URLs
+* "Honeypot" bot detection for registration and contact forms.
+
+### New Features
+* #1429 Search engine friendly topic URLs
+* Implemented cookie consent according to EU-GDPR
+* Added checkboxes for data processing consent in all relevant forms
+* Implemented "Honeypot" bot detection for registration and contact forms.
+* Trusted Shops: Added consent checkbox to confirm order page for submission of customer email address to Trusted Shops if review widget is active
+* #1226 Shop-Connector: Added exchange of tier prices and delivery times
+* #1439 Debitoor: Option whether to display the payment method and SKU on invoices
+
+### Improvements
+* Added double opt-in feature for newsletter subscriptions during checkout (confirm order)
+* Allow forward slash in product tag URL slug
+* Theming: throttle AJAX cart refresh after spin up/down click
+* Moved StoreLastIpAddress & DisplayPrivacyAgreementOnContactUs from customer settings to privacy settings tab
+* #1450 Show the regular price only if it's higher than the final price
+* #1450 Do not ignore discounts with a negative amount
+* (Soft) deleted customers can be edited now
+* Customer IP addresses will be anonymized on (soft) deletion
+* Set catalogsettings.showsharebutton to false as its not compliant with GDPR
+* Made form fields for first & last name in customer registration optional
+* Implemented settings to make form fields for first & last name required again
+* Made form field for full name in contact us & product request optional
+* Implemented settings to make form field for full name in contact us & product request required again
+* #1453 Import: Use [IGNORE] to ignore a field value on record level
+* #1455 More detail on packing slip when bundled item
+* Display category alias as badge in grids and dropdowns
+
+### Bugfixes
+* Migration: take all same-named message templates into account
+* Messaging: OrderPlaced e-mail templates show main product image even when an attribute combination with a custom image has been selected
+* Theming: fix broken product review voting
+* Theming: added missing bottom space to .html-editor-content
+* Theming: Language switcher is not displayed if no currency options are available
+* No bundle item thumbnail displayed in bundle summary if item is not individually visible
+* Tracking number in shipment details was not saved
+* Assigning or removing product tags did not invalidate model cache
+* Reward points weren't displayed in message templates
+* Dashboard: link for not yet shipped orders loads list with all orders
+* Topic search button had no effect
+* #1442 Message factory should not throw an exception if a template has been deactivated
+* Fixes script error "$(...).tab is not a function" on product detail page
+* Title attribute for the product name in product lists was sometimes truncated
+* Relativizing font sizes should cast to double, not int
+* Fixes category list on product edit page shows empty category name
+* #1438 Debitoor: The country is displayed twice
+* MegaSearch: Fixes indexing ignores DeliveryTimeIdForEmptyStock setting
+* Web API: Fixes "No NavigationLink factory was found for the navigation property 'WalletHistory'"
+* #1449 IgnoreCharges of shipping methods is not working if a localized name is specified
+* Fixes "The object does not support the property or method 'startsWith'" on product edit page.
+* Wallet: Fixes "Child actions are not allowed to perform redirect actions" when there are cart warnings
+* Fixes the delivery time in the order notifications may differ from delivery time on the product detail page
+
+
+## SmartStore.NET 3.1.0
-### Breaking change
-* Removed Web API plugin from open source core
+### Highlights
+* **Wallet**: Enables full or partial order payment via credit account. Includes REST-Api. (commercial plugin)
+* **[Liquid](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers) template engine**: very flexible templating for e-mails and campaigns with autocompletion and syntax highlighting.
+* **Cash Rounding**: define money rounding rules on currency and payment method level.
+* **Modern, responsive backend**: migrated backend to Bootstrap 4, overhauled and improved the user interface.
+* **Enhanced MegaMenu**: virtual dropdowns for surplus top-level categories and brands (commercial plugin exclusively bundled with Pro Edition).
+* **RTL**: comprehensive RTL (Right-to-left) and bidi(rectional) support.
+* **Amazon Pay**:
+ * Supports merchants registered in the USA and Japan
+ * External authentication via *Login with Amazon* button in shop frontend
+ * Several improvements through the new *Login and pay with Amazon* services
+* (Perf) **Faster image processing**: new processing and caching strategy! Thumbnails are not created synchronously during the main request anymore, instead a new middleware route defers processing until an image is requested by any client.
+* **TinyImage**: scores ultra-high image compression rates (up to 80 %!) and enables WebP support (commercial plugin exclusively bundled with Premium Edition).
+* **UrlRewriter**: define URL redirection rules in the backend using *mod_rewrite* notation. (commercial plugin)
+* **Address formatting** templates by country
+* **Language packs**: downloader & auto-importer for packages available online.
+
+### Breaking changes
+* Message template customizations are lost due to the new template engine. You have to customize the templates again. No automatic migration, sorry :-(
+* Amazon Pay: The plugin has been changed to new *Login and pay with Amazon* services. The client ID has been added, which has to be created in Amazon Seller Central and saved in the payment method configuration.
+* (Dev) Calls to cache methods `Keys()` and `RemoveByPattern()` require glob chars to be present now (supported glob-styles see [https://redis.io/commands/keys](https://redis.io/commands/keys)). Previously these methods appended `*` to the passed pattern, which made pattern matching rather unflexible.
+* (Dev) Hook framework now passes `IHookedEntity` interface instead of `HookedEntity` class
+* (Dev) Completely removed all `EntityInserted`, `EntityUpdated` and `EntityDeleted` legacy events. We were using DbSaveHooks anyway, which provides a much more powerful and way faster pub-sub mechanism for database operations.
### New Features
-* Output Cache with "donut hole caching" (commercial plugin)
-* REDIS providers for Business Cache, Output Cache and Session State (commercial plugin)
-* Microsoft AZURE provider for media storage (commercial plugin)
+* 1203 MegaMenu shrinker and *Brands* virtual menu item
+* [Summernote](https://summernote.org/) is now the primary HTML editor
+* #431 Added option to randomize the display order for slides on each request
+* #1258 Add option to filter shipping and payment methods by a specific customer role
+* #1247 Allow to import non system customer roles in customer import
+* #1117 Added an option to display a dropdown menu for manufacturers
+* #1203 Added an option to define a maximum number of elements in the main menu for the first hierarchy of the catalog navigation
+* GMC: column chooser for edit grid
+* #1100 Customer can register in frontend via "Login with Amazon" button
+* **Web API**:
+ * #1292 Added endpoint to get order in PDF format
+ * Added endpoint to complete an order
+ * #1364 Added endpoints for MeasureWeight and MeasureDimension
+* Added options to include option names of specification and product attributes in the search index
+* #441 added option to specify that additional shipping surcharges are considered only once.
+* #1295 Sales tracking (tracking pixel) for Billiger.de
+* XML and CSV export of shopping cart and wishlist items
+* #1363 Make storing of IP addresses optional
+* #729 Option for automatic order amount capturing when the shipping status changed to "shipped"
+* (Dev) ILocalizationFileResolver: responsible for finding localization files for client scripts
+* #998 GMC: Find a way to map attribute combination values to feed export values
+* Added Instagram icon to social media icons in footer
+
+### Improvements
+* Target .NET Framework changed: 4.5.2 > 4.6.1.
+* Lower memory consumption
+* #649 Media FileSystem provider: segmenting files in subfolders to increase IO perf with huge amount of files
+* #1141 Cleaner backend order list. Added more infos like payment method.
+* OuputCache: Simple product changes that affect visibility now correctly invalidate all assigned category and manufacturer pages
+* * OuputCache: When MegaSearch is active, invalidation occurs only during indexing and not ad-hoc anymore.
+* #1248 New payment integration guidelines for Sofort\Klarna
+* TwitterAuth: better error handling and enhanced admin instruction
+* #1181 Debitoor: Add option to display shipping address on invoices
+* Moved RoundPricesDuringCalculation setting to currency entity
+* #1100 Use new "Login with Amazon" services to initialize an Amazon payment
+* #1285 Copy product: Add option to add more than one copy
+* (Perf) Many improvements in hooking framework
+* #1294 Swiss PostFinance: External payment page too small on mobile devices. Added setting for mobile device template URL, pre-configured with PostFinance template.
+* #1143 Make shipping methods suitable for multi-stores
+* #1320 Image import: Find out the content type of image URLs by response header rather than file extension (which is sometimes missing)
+* #1219 Recently viewed products list should respect setting to hide manufacturer names
+* Import and export product quantity step
+* Add bundle item information to order messages
+* #1031 Enable offline payment methods to have brand icons
+* DevTools Plugin: Added example for cached output invalidation
+
+### Bugfixes
+* #1268 Data importer always inserts new pictures and does not detect equal pictures while importing
+* OutputCache computes ambiguous cache keys for blog pages
+* #1142 Customer import creates role multiple times
+* #1244 Variant query model binder cannot handle types text and datepicker
+* #1273 Attribute formatter should consider setting CatalogSettings.ShowVariantCombinationPriceAdjustment
+* Product entity picker should use the wildcard search to find products
+* Hook framework should run hooks with `ImportantAttribute` when hooking was disabled per scope
+* #1297 Web API: Parsing the timestamp may fail due to the different accuracy of the milliseconds
+* Debitoor: VAT amount could be transmitted as miscellaneous for deliveries abroad.
+* Prices with discounts limited to categories and customer groups were shown to all users in product lists
+* #1330 MegaSearch: Missing variant facets if the variant value is not unique
+* Back-in-stock subscription form was already submitted when opening the popup dialog
+* Associated products of a grouped product were displayed in the wrong order
+* Payment-Filter: Fixed "The cast to value type 'System.Decimal' failed because the materialized value is null"
+* The tax value per tax rate was not updated when adding\removing a product to\from the order.
+* The option to send manually was ignored when sending e-mails
+* #528 LimitedToStores is required on payment provider rather than plugin level
+* #1318 Disabled preselected attribute combination permanently hides the shopping cart button, even if another combination is selected.
+* Copy product: Fixes "Cannot insert duplicate key row in object dbo.UrlRecord with unique index IX_UrlRecord_Slug"
+* Fixed export publishing via email
+
+## SmartStore.NET 3.0.3
+### Bugfixes
+* #1232 Scheduled tasks are being 'randomly' set inactive
+* #1245 Existing product does not get updated during import under certain circumstances
+* #1207 No low stock activity when bulk editing the product quantity
+* #1208 Missing inventory tab when switching from bundle to simple product
+* #1210 Clear asset cache when a theme setting has changed
+* Fixed name of deleted manufacturers displayed in product lists
+* Fixed non-system category export profile always exports categories of current store
+* Fixed picture assignment for variant attribute combinations of product copies which weren't assigned correctly
+* #1218 CopyProductService does not copy picture of ProductVariantAttributeValue
+* Fixed hidden checkout button when there are two or more shopping cart items and one gets removed from basket during checkout
+* Fixed physical gift cards cannot be moved to cart
+* Fixed tax rate wasn't properly formatted on product detail page
+* #1228 Text entered in frontend for attributes or checkout attributes are truncated by comma
+* Fixed shipping computation method ignoring deactivated PricesIncludeTax setting
+* **Debitoor**: Fixed missing tax rates on Debitoor invoice for net prices
+* #1224 Notifier wasn't working in plugin controllers
+* #1205 Server cannot append header after
+*
+* headers have been sent
+* #1154 Left offcanvas navigation does not open when in checkout progress
+* #1212 Export: FTP publishing should consider directory structure
+* #1253 Product PDF exporter only exports one picture and ignores the picture number profile setting
+* Configuration of some plugins not saved in Firefox browser
+
+### Improvements
+* Apply img-fluid class to all images within html editor content
+* System name in privacy info topic URL should be lowercase
+* If no checkout attribute is required checking out will be possible
+* **OpenTrans**: optimizations for Lexware import
+* #496 show bonus points in ordersummary on first checkout page
+* Discounts of plugins weren't displayed in product lists
+
+### New Features
+* #1076 Add support for percental tier price adjustments
+* **Trusted Shops**: added review import
+* (Dev) SSL redirection on localhost will be bypassed
+* #783 Added option for "Email a friend" which disables the form field "Your email address" so it can't be changed
+
+## SmartStore.NET 3.0.2
+### Breaking changes
+* MegaSearch: The index must be re-created because of changes to the price filter.
+
+### New Features
+* New setting indicates whether to include or exclude unavailable products in search results by default.
+* (Dev) New Asset Cache
+
+### Improvements
+* PayPal Standard: New settings "UsePayPalAddress" and "IsShippingAddressRequired" to avoid payment rejection due to address validation.
+* More widget zones for the footer
+* #1177 Implement new setting for product search sorting
+* More fault tolerant task scheduler
+
+### Bugfixes
+* Fixed memory leak in MegaSearch plugin, which caused HTTP 503
+* RSS: Item URLs in feeds have ignored SSL settings
+* Fixed "The view 'ProfileImportResult' or its master was not found"
+* #1187 Search: Do not hide multi-selectable filters of the same group if there are no hits
+* Fixed System.ArgumentNullException in ProductVariantQueryFactory
+* PayPal PLUS: Fixed HTTP 401 "Unauthorized" when calling PatchShipping
+* #1189 MegaSearch: Boosts are ignored in prefix and wildcard queries by default
+* MegaSearch: Localized labels of filters were never displayed
+* #1195 Exporter: don't send an email if no email account has been selected
+* Product lists sometimes show the wrong delivery time
+* #1192 Lucene indexing
+*
+* decreases the longer it takes
+* #1198 MegaSearch: never sort numeric range by label, always by value
+* Filter for attributes were always sorted by hit count
+* #1200 PayPal PLUS: Invalid request if the order amount is zero
+* Fixed null reference exception when copying attribute option set with image(s) (file system storage only)
+* Product price sometimes was wrong when entering a numeric value in an attribute text box
+* Added missing code for customer privacy agreement
+* Clicking the login link wasn't working correctly in offcanvas my-account menu
+* #1158 Currency and language selectors weren't working in OffCanvas menu
+* Fixed "Server cannot append header after HTTP headers have been sent"
+
+## SmartStore.NET 3.0.1
+### Improvements
+* (Perf) Much faster application startup
+* Microsoft Visual C++ 2015 Redistributable no longer required to be installed
+* **BeezUP**:
+ * Exports up to 9 product images
+ * Export stock quantity when stock is managed by attributes
+ * Export parent child relation data when exporting attribute combinations as products
+ * Flatten and append attribute data when exporting attribute combinations as products
+* Instant search should search manufacturer/brand name
+* HTTP 301 redirects for legacy media URLs
+* (Dev) New 'ApplicationStarted' event
+* (Dev) Enabled C# 6 features in plugin views
+* (GMC) Added missing database indexes.
+* (WebApi) Added endpoints for blog post and blog comment
+* Added more sortable columns to backend product grids
+* #1160 Append suffix 'Picture (1...n)' to thumbnail's alt-attribute on product detail pages
+* (Perf) Faster catalog indexing thanks to new database indexes
+* (Perf) Faster dynamic view compilation thanks to *Roslyn* compiler
+* Added the widget zone 'productdetails_pictures_bottom'
+* Added config setting *sm:PdfEngineBaseUrl*. There are cases where the PDF converter exits with a network error, when it is unable to load automatically resolved URLs.
+* (Dev) Added *Retry* utility class
+* #1176 Admin > Product Search: It ain't possible to search for parts of a product name
+
+### Bugfixes
+* #1145: Fixed HTTP 404 after switching language
+* Fixed null reference exception in product lists if sorting is not allowed
+* The file manager did not work correctly under a virtual folder
+* Item row in list style product list should not wrap on small devices
+* Item row in list style product list squashed on Android system browser
+* Linq search: Filter for manufacturers and categories were not limited by store
+* (GMC) Fixes duplicate occurrence of application path in absolute product URL
+* Products with stock managed by attributes sometimes not displayed in category lists
+* Fixed System.MissingMethodException for SmartStore.Core.Search.Facets.FacetGroup
+* Fixed uploaded image paths in HTML fields (Media/Uploaded > Media//Uploaded)
+* (MegaSearch) Product category order, product manufacturer order and product order were not recognized (sorting)
+* Standard search: Fixed sorting of recently added products
+* Alias for search filters couldn't be set if there's only one language
+* #1168 Cart: MinOrderAmount sometimes ignored
+* Moving products from editable wishlist to cart was out of function
+* Fixed several issues with parallelly executed data export tasks
+* Fixed several issues with *PayPal* payment providers
+* Facebook login out of function due to Facebook API changes (always returns "Unknown error")
+* Fixed 'no picture available' watermark on some product pictures
+* #1153 Removing already applied gift card causes error
+* (Theming) *Drift* zoom uses static zoomFactor. Made it dynamic.
+* (Theming) IE11: simple menu dropdowns weren't positioned correctly
+* Removed "trust" element from web.config (causes problems on some shared hosting spaces)
+* Fixed ThumbZoomer unproportional rescale issue in product grid
+* #1134 JavaScript error (IE 11 in VS debug mode) after removing the last item in shopping cart
+* (Theming) Dozens of layout fs for ixemobile devices (especially iOS and Android native browser)
+* Better error logging for TaskScheduler
+* Fixed several queries which caused problems with SQL CE
+* Fixed missing CurrencyCode exception in product exports
+* #1179 Error while downloading a downloadable product
+* Mega Menu: If a dropdown contains two level hierarchy categories only, they are not wrapped to the next column
+* Hide sort dropdown when option is off
+* HTTP 301 redirects for legacy media URLs
+* Added missing resources of broken migration 'AddressEnhancement' again
+* Checkout: Title and salutation were missing when entering new addresses
+* #1163 ContentSlider: pictures won't be displayed when placed on top or bottom on IOS
+
+## SmartStore.NET 3.0
+
+### Highlights
+* **Flex**: New mobile-first responsive Theme based on Bootstrap 4
+* **Mega Search**: highly professional search framework based on Lucene.NET (commercial plugin exclusively bundled with Pro Edition)
+ * Ultra fast search results, even with millions of items
+ * Faceted search
+ * Synonyms
+ * Compound word splitting
+* **Mega Menu**: highly customizable catalog menu widgets (commercial plugin exclusively bundled with Pro Edition)
+* **Content Slider**: creates eye-catching content to boost sales (commercial plugin exclusively bundled with Pro Edition)
+* **Output Cache** with "donut hole caching" for maximum speed and scalability (commercial plugin exclusively bundled with Premium Edition)
+* **Microsoft AZURE** provider for media storage (commercial plugin exclusively bundled with Premium Edition)
+* Web Farms: **REDIS** providers for Business Cache, Output Cache and Session State (commercial plugin exclusively bundled with Enterprise Edition)
+* Product variant **option sets**
+* New product specification attribute type: **numeric range**
+* Image support for variant attributes
+
+### Breaking changes
+* Removed old **Content Slider** module from open source core
+* Removed **Web API** plugin from open source core (now exclusively bundled with Premium Edition)
+
+
+### Other New Features
* Message Bus for inter-process messaging between servers (commercial plugin)
-* Configurable media storage path for web farms
-* Added option to skip shipping method selection in checkout process when only one shipping method is active
+* Configurable media storage path for web farms
+* (Dev) log4net integration
+* (Dev) proper plugin view debugging
+* (Dev) detect changes to static plugin files (css, js, etc.) and immediately apply them to running project
+* (Dev) Theming: plugins now support implicitly imported SASS files (Content/[public|admin].scss)
+* XML Sitemap optimized for very large catalogs:
+ * Partitions for very large sitemaps (> 50.000 nodes or > 10 MB)
+ * Generated in a background task. No instant invalidation anymore.
+* Added option to skip shipping method selection in checkout process if only one shipping method is active
* Added options to capture salutation and title in addresses and customer info
* Added projection to control the export of individually visible associated products
-* #1002 Web API: Add support for addresses and customer roles navigation property of customer entity
-* #966 Implement new tax calculation logic for shipping and payment fees (Calculate with rate of highest cart amount)
-* #922 New option whether to include the weight of free shipping products in shipping by weight calculation
+* #966 Implement new tax calculation logic for shipping and payment fees (calculate with rate of highest cart amount)
+* #922 New option to specify whether to include the weight of free shipping products in shipping by weight calculation
* #724 Allow discounts to be applied to manufacturers
+* Option to display manufacturers sorted in alphabetical order
+* #1093 New product properties required for selling abroad
+* Web API:
+ * #1002 Add support for addresses and customer roles navigation property of customer entity
+ * #1062 Add filter options for user grid on configuration page
+ * #1072 Add support for TaxCategory
+ * #1073 Settings for maximum pagesize ($top) and maximum expansion depth ($expand)
+ * #1074 Extend product image upload to allow updating of images
+ * #1064 Deleting all product categories/manufacturers per product in one go
+ * #1063 Adding product category/manufacturer ignores any other property like DisplayOrder
+ * Added endpoint "Infos" for order and order item entity for additional information like aggregated data.
+ * Swagger integration
+* Added setting to specify whether the product manufacturer should be displayed in product detail
+* #271 Implemented support for attribute images/icons
+* #330 Implement 'attribute option sets'
+* Two themes Flex Black and Flex Blue which are derived from Flex theme
+
### Improvements
+* Updated .NET Target Framework from 4.5.1 to 4.5.2
* Added order message token for accepting third party email handover
* ECB currency exchange rate provider now cross calculates rates based on euro rates
* BeezUP: Exports the product weight
-* Payone: Replace client API by Payone iFrame solution. Allows credit card payment compliant with PCI DSS, SAQ A.
* Updated Elmar shop info XML from version 1.1 to 2.0
+* (Perf) Application start faster by ~20%
+* (Perf) Lower memory consumption
+* (Perf) #1098 Reduce number of created guest accounts (more reliable bot detection)
* #1008 Export: Add support for description projection to all product exporting providers
* #1015 Implement Entity Picker in discount requirements
+* Debitoor improvements:
+ * Company name send as customer name if present
+ * Option to append customer email address to invoice address
+ * Additional order notes for tax exemption within and outside EU
+* #1048 Add meta tags to contact us page
+* Order XML export includes more customer data like title and salutation
+* #1080 Do not resolve Order.OrderURLForCustomer token for guest customers
+* #1051 Display EAN in PDF packaging slip
+* #806 User couldn't be created in the admin section without "Manage Customer Roles" permission
+* #491 Implemented option to determine a default delivery time
+* #1078 Implemented options for "Add to Cart" quantity input field on product level. Quantity control can now be hidden and can be configured to be increased and decreased in steps.
+* #696 Added textual resources for meta title and description for blog per month and by tag pages and enriched them with corresponding month plus year respectivly tag name
+* #1025 Added token for customer number to MessageTokenProvider
+* #1016 Implemented choosing of MessageTokens for newsletter campaigns with dropdown menu
+* #1107 OpenTrans: Added option to exclude long description of products from export
+* BMEcat: Implemented download of pictures according to MimeRoot-Info
+* Payone:
+ * Replace client API by Payone iFrame solution. Allows credit card payment compliant with PCI DSS, SAQ A.
+ * #1123 Add order note with payment info for prepayment and invoice payment.
+
### Bugfixes
-* Currency wasn't displayed at shipping estimation
-* SKU, EAN, MPN of last attribute combination was exported for all combinations
-* GMC: Id should be unique when exporting attribute combinations as products
-* GMC: Attribute price adjustments were ignored when exporting attribute combinations as products
-* GMC: Associated products that are not individually visible are not exported anymore. GMC rejects them because the frontend redirects to the grouped product.
-* #999 Export: Projected customer id ignored during price calculation
-* Awarded reward points for a placed order sometimes wrong calculated
-* PayPal PLUS: A changed shipping address/costs was not transmitted to PayPal
+* Currency wasn't displayed for shipping estimation
+* SKU, EAN, MPN of last attribute combination were exported for all combinations
+* GMC:
+ * Id should be unique when exporting attribute combinations as products
+ * Attribute price adjustments were ignored when exporting attribute combinations as products
+ * Associated products that are not individually visible are not exported anymore. GMC rejects them because the frontend redirects to the grouped product.
+* Export:
+ * #999 Projected customer id was ignored during price calculation
+ * #1104 Language projection was ignored when creating product details URL
+ * #1030: Orders didn't include data of attribute combinations
+ * Orders didn't include shipping address
+* Awarded reward points for a placed order were occasionally calculated wrong
+* PayPal PLUS:
+ * Changed shipping address/costs were not transmitted to PayPal
+ * Skip payment if cart total is zero
+ * Do not display payment wall if method is filtered
+* PayPal Express: Fixed net price issue.
* Bundle item cannot be deleted if it's in a shopping cart
-
+* Fixed SSL issue for news items in RSS feed
+* Filter shows wrong number of products if "Include products from subcategories" is activated
+* Guest user cannot be deleted if he has a private message
+* #1029 Biz-Importer: Imports fixed tax rates as zero percentage
+* #1055 Checkout: Localized shipping method names are not displayed if shipping by total is activated
+* #1071 NewsLetterSubscription.ActivationUrl not working if the store if SSL secured
+* #1086 Gift cards can be earned in one store and applied in another
+* Whishlist: Products with minimum order amount greater then 1 can be added to the whishlist without any warning
+* #1102: Discounts not transmitted when the total amount is zero
+* #1101 Clickatell: Sending SMS not working anymore
## SmartStore.NET 2.6
@@ -170,7 +762,8 @@
* #495 Implement option to search product detail description by default
### Improvements
-* (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%.
+* (Perf) Implemented static caches for URL aliases and localized properties. Increases
+* and request speed by up to 30%.
* (Perf) Significantly reduced number of database reads during product list rendering. Increases request speed by up to 10%.
* (Perf) Implemented 2nd level cache for infrequently changed entities. Increases request speed by up to 10%.
* TaskScheduler: Rewritten from scratch to be suitable for Web Farms
@@ -790,7 +1383,7 @@
* [SMNET-202] - SmartTabSelection mit verschachtelten Tabs fehlerhaft nach Reload einer Seite
### Improvement
-
+
* [SMNET-13] - Attributwerte: der Text "Aufpreis" muss um "Minderpreis" erweitert werden.
* [SMNET-15] - Umgestaltung der Darstellung der Staffelpreise (Popover ab dem fünften Element)
* [SMNET-30] - MessageTemplates teilweise auf Englisch
diff --git a/lib/ClearScript.V8/Native/amd64/msvcp110.dll b/lib/ClearScript.V8/Native/amd64/msvcp110.dll
deleted file mode 100644
index 7e278956df..0000000000
Binary files a/lib/ClearScript.V8/Native/amd64/msvcp110.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/Native/amd64/msvcr110.dll b/lib/ClearScript.V8/Native/amd64/msvcr110.dll
deleted file mode 100644
index dd484a5885..0000000000
Binary files a/lib/ClearScript.V8/Native/amd64/msvcr110.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/Native/x86/msvcp110.dll b/lib/ClearScript.V8/Native/x86/msvcp110.dll
deleted file mode 100644
index 93fab5683b..0000000000
Binary files a/lib/ClearScript.V8/Native/x86/msvcp110.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/Native/x86/msvcr110.dll b/lib/ClearScript.V8/Native/x86/msvcr110.dll
deleted file mode 100644
index 1ce960d750..0000000000
Binary files a/lib/ClearScript.V8/Native/x86/msvcr110.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/lib/ClearScriptV8-32.dll b/lib/ClearScript.V8/lib/ClearScriptV8-32.dll
deleted file mode 100644
index c8c9a30b3f..0000000000
Binary files a/lib/ClearScript.V8/lib/ClearScriptV8-32.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/lib/ClearScriptV8-64.dll b/lib/ClearScript.V8/lib/ClearScriptV8-64.dll
deleted file mode 100644
index 450033d364..0000000000
Binary files a/lib/ClearScript.V8/lib/ClearScriptV8-64.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/lib/v8-ia32.dll b/lib/ClearScript.V8/lib/v8-ia32.dll
deleted file mode 100644
index fcc2956bcb..0000000000
Binary files a/lib/ClearScript.V8/lib/v8-ia32.dll and /dev/null differ
diff --git a/lib/ClearScript.V8/lib/v8-x64.dll b/lib/ClearScript.V8/lib/v8-x64.dll
deleted file mode 100644
index a1b9ca7c8e..0000000000
Binary files a/lib/ClearScript.V8/lib/v8-x64.dll and /dev/null differ
diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll
index a56aac44b3..17bf4e2ad9 100644
Binary files a/lib/SmartStore.Licensing/SmartStore.Licensing.dll and b/lib/SmartStore.Licensing/SmartStore.Licensing.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-console-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-console-l1-1-0.dll
new file mode 100644
index 0000000000..b469ccbd58
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-console-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-datetime-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-datetime-l1-1-0.dll
new file mode 100644
index 0000000000..d0cdea4bb6
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-datetime-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-debug-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-debug-l1-1-0.dll
new file mode 100644
index 0000000000..11283e3db3
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-debug-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-errorhandling-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-errorhandling-l1-1-0.dll
new file mode 100644
index 0000000000..1069800f35
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-errorhandling-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-file-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-file-l1-1-0.dll
new file mode 100644
index 0000000000..b608d0c7aa
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-file-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-file-l1-2-0.dll b/lib/msvcp140/x64/api-ms-win-core-file-l1-2-0.dll
new file mode 100644
index 0000000000..620539cb6e
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-file-l1-2-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-file-l2-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-file-l2-1-0.dll
new file mode 100644
index 0000000000..16f3d88c6b
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-file-l2-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-handle-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-handle-l1-1-0.dll
new file mode 100644
index 0000000000..cbd019ace3
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-handle-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-heap-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-heap-l1-1-0.dll
new file mode 100644
index 0000000000..963f042adc
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-heap-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-interlocked-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-interlocked-l1-1-0.dll
new file mode 100644
index 0000000000..62d0d8ab33
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-interlocked-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-libraryloader-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-libraryloader-l1-1-0.dll
new file mode 100644
index 0000000000..054fca5544
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-libraryloader-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-localization-l1-2-0.dll b/lib/msvcp140/x64/api-ms-win-core-localization-l1-2-0.dll
new file mode 100644
index 0000000000..474775600f
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-localization-l1-2-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-memory-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-memory-l1-1-0.dll
new file mode 100644
index 0000000000..41b3a5bbc0
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-memory-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-namedpipe-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-namedpipe-l1-1-0.dll
new file mode 100644
index 0000000000..c89a74c7bb
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-namedpipe-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-processenvironment-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-processenvironment-l1-1-0.dll
new file mode 100644
index 0000000000..a820084ebe
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-processenvironment-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-0.dll
new file mode 100644
index 0000000000..debbd17a3c
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-1.dll b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-1.dll
new file mode 100644
index 0000000000..f7a2395b80
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-1.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-profile-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-profile-l1-1-0.dll
new file mode 100644
index 0000000000..99d2336dad
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-profile-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-rtlsupport-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-rtlsupport-l1-1-0.dll
new file mode 100644
index 0000000000..2352c908c4
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-rtlsupport-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-string-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-string-l1-1-0.dll
new file mode 100644
index 0000000000..2af9f458f9
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-string-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-synch-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-synch-l1-1-0.dll
new file mode 100644
index 0000000000..86a893a946
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-synch-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-synch-l1-2-0.dll b/lib/msvcp140/x64/api-ms-win-core-synch-l1-2-0.dll
new file mode 100644
index 0000000000..70d2f4b705
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-synch-l1-2-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-sysinfo-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-sysinfo-l1-1-0.dll
new file mode 100644
index 0000000000..400883dd7d
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-sysinfo-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-timezone-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-timezone-l1-1-0.dll
new file mode 100644
index 0000000000..3543bf6b86
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-timezone-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-core-util-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-util-l1-1-0.dll
new file mode 100644
index 0000000000..3932409597
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-util-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-conio-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-conio-l1-1-0.dll
new file mode 100644
index 0000000000..fee9507d8c
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-conio-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-convert-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-convert-l1-1-0.dll
new file mode 100644
index 0000000000..e398c3b6da
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-convert-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-environment-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-environment-l1-1-0.dll
new file mode 100644
index 0000000000..ee25a451bc
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-environment-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-filesystem-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-filesystem-l1-1-0.dll
new file mode 100644
index 0000000000..d8e225f362
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-filesystem-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-heap-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-heap-l1-1-0.dll
new file mode 100644
index 0000000000..f568d6d5b1
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-heap-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-locale-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-locale-l1-1-0.dll
new file mode 100644
index 0000000000..d11e4ba9ab
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-locale-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-math-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-math-l1-1-0.dll
new file mode 100644
index 0000000000..514e2d4530
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-math-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-multibyte-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-multibyte-l1-1-0.dll
new file mode 100644
index 0000000000..1234fb96b8
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-multibyte-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-private-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-private-l1-1-0.dll
new file mode 100644
index 0000000000..95c2067a08
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-private-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-process-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-process-l1-1-0.dll
new file mode 100644
index 0000000000..abbbf7cabf
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-process-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-runtime-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-runtime-l1-1-0.dll
new file mode 100644
index 0000000000..87f9144ab5
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-runtime-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-stdio-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-stdio-l1-1-0.dll
new file mode 100644
index 0000000000..a03d17c160
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-stdio-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-string-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-string-l1-1-0.dll
new file mode 100644
index 0000000000..19fff9dae1
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-string-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-time-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-time-l1-1-0.dll
new file mode 100644
index 0000000000..faf52046b6
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-time-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/api-ms-win-crt-utility-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-utility-l1-1-0.dll
new file mode 100644
index 0000000000..772b23e15e
Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-utility-l1-1-0.dll differ
diff --git a/lib/msvcp140/x64/concrt140.dll b/lib/msvcp140/x64/concrt140.dll
new file mode 100644
index 0000000000..915a13a3a1
Binary files /dev/null and b/lib/msvcp140/x64/concrt140.dll differ
diff --git a/lib/msvcp140/x64/msvcp140.dll b/lib/msvcp140/x64/msvcp140.dll
new file mode 100644
index 0000000000..4a5ca4f47a
Binary files /dev/null and b/lib/msvcp140/x64/msvcp140.dll differ
diff --git a/lib/msvcp140/x64/ucrtbase.dll b/lib/msvcp140/x64/ucrtbase.dll
new file mode 100644
index 0000000000..4c0f926221
Binary files /dev/null and b/lib/msvcp140/x64/ucrtbase.dll differ
diff --git a/lib/msvcp140/x64/vccorlib140.dll b/lib/msvcp140/x64/vccorlib140.dll
new file mode 100644
index 0000000000..d3cc8e00f1
Binary files /dev/null and b/lib/msvcp140/x64/vccorlib140.dll differ
diff --git a/lib/msvcp140/x64/vcruntime140.dll b/lib/msvcp140/x64/vcruntime140.dll
new file mode 100644
index 0000000000..52fe06bd61
Binary files /dev/null and b/lib/msvcp140/x64/vcruntime140.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-console-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-console-l1-1-0.dll
new file mode 100644
index 0000000000..eaedb39ccb
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-console-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-datetime-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-datetime-l1-1-0.dll
new file mode 100644
index 0000000000..211c00fabf
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-datetime-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-debug-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-debug-l1-1-0.dll
new file mode 100644
index 0000000000..204a23e2c9
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-debug-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-errorhandling-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-errorhandling-l1-1-0.dll
new file mode 100644
index 0000000000..7312ab3827
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-errorhandling-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-file-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-file-l1-1-0.dll
new file mode 100644
index 0000000000..a345dc6e71
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-file-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-file-l1-2-0.dll b/lib/msvcp140/x86/api-ms-win-core-file-l1-2-0.dll
new file mode 100644
index 0000000000..8dfc671c82
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-file-l1-2-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-file-l2-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-file-l2-1-0.dll
new file mode 100644
index 0000000000..5000625959
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-file-l2-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-handle-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-handle-l1-1-0.dll
new file mode 100644
index 0000000000..8e4d1ac4a7
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-handle-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-heap-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-heap-l1-1-0.dll
new file mode 100644
index 0000000000..2f8dba8767
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-heap-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-interlocked-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-interlocked-l1-1-0.dll
new file mode 100644
index 0000000000..abf946796e
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-interlocked-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-libraryloader-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-libraryloader-l1-1-0.dll
new file mode 100644
index 0000000000..ad39dbc73b
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-libraryloader-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-localization-l1-2-0.dll b/lib/msvcp140/x86/api-ms-win-core-localization-l1-2-0.dll
new file mode 100644
index 0000000000..df914950ca
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-localization-l1-2-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-memory-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-memory-l1-1-0.dll
new file mode 100644
index 0000000000..7f6cd01ce6
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-memory-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-namedpipe-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-namedpipe-l1-1-0.dll
new file mode 100644
index 0000000000..d1d0e7771d
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-namedpipe-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-processenvironment-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-processenvironment-l1-1-0.dll
new file mode 100644
index 0000000000..88a0809e05
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-processenvironment-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-0.dll
new file mode 100644
index 0000000000..99dbfe6024
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-1.dll b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-1.dll
new file mode 100644
index 0000000000..2c8315bdeb
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-1.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-profile-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-profile-l1-1-0.dll
new file mode 100644
index 0000000000..fbdc2fa98a
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-profile-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-rtlsupport-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-rtlsupport-l1-1-0.dll
new file mode 100644
index 0000000000..5538941b0c
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-rtlsupport-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-string-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-string-l1-1-0.dll
new file mode 100644
index 0000000000..ef94ad4d27
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-string-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-synch-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-synch-l1-1-0.dll
new file mode 100644
index 0000000000..f3abf477c5
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-synch-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-synch-l1-2-0.dll b/lib/msvcp140/x86/api-ms-win-core-synch-l1-2-0.dll
new file mode 100644
index 0000000000..239ee9f913
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-synch-l1-2-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-sysinfo-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-sysinfo-l1-1-0.dll
new file mode 100644
index 0000000000..7b4e1c9858
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-sysinfo-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-timezone-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-timezone-l1-1-0.dll
new file mode 100644
index 0000000000..feb24fc43f
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-timezone-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-core-util-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-util-l1-1-0.dll
new file mode 100644
index 0000000000..fedd4038bd
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-util-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-conio-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-conio-l1-1-0.dll
new file mode 100644
index 0000000000..08f68be2c2
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-conio-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-convert-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-convert-l1-1-0.dll
new file mode 100644
index 0000000000..5b4e62cc1d
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-convert-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-environment-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-environment-l1-1-0.dll
new file mode 100644
index 0000000000..8009912352
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-environment-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-filesystem-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-filesystem-l1-1-0.dll
new file mode 100644
index 0000000000..a4414560b5
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-filesystem-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-heap-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-heap-l1-1-0.dll
new file mode 100644
index 0000000000..2584aad585
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-heap-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-locale-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-locale-l1-1-0.dll
new file mode 100644
index 0000000000..9dcda52733
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-locale-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-math-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-math-l1-1-0.dll
new file mode 100644
index 0000000000..d916a4df87
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-math-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-multibyte-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-multibyte-l1-1-0.dll
new file mode 100644
index 0000000000..a6f53c2a62
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-multibyte-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-private-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-private-l1-1-0.dll
new file mode 100644
index 0000000000..7337a284a4
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-private-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-process-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-process-l1-1-0.dll
new file mode 100644
index 0000000000..e949c9aa8d
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-process-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-runtime-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-runtime-l1-1-0.dll
new file mode 100644
index 0000000000..a0930f4199
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-runtime-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-stdio-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-stdio-l1-1-0.dll
new file mode 100644
index 0000000000..7a47fa3aa1
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-stdio-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-string-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-string-l1-1-0.dll
new file mode 100644
index 0000000000..a00fd9eee2
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-string-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-time-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-time-l1-1-0.dll
new file mode 100644
index 0000000000..4b3c00b6d2
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-time-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/api-ms-win-crt-utility-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-utility-l1-1-0.dll
new file mode 100644
index 0000000000..b285e9cafc
Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-utility-l1-1-0.dll differ
diff --git a/lib/msvcp140/x86/concrt140.dll b/lib/msvcp140/x86/concrt140.dll
new file mode 100644
index 0000000000..4f9ed11ade
Binary files /dev/null and b/lib/msvcp140/x86/concrt140.dll differ
diff --git a/lib/msvcp140/x86/msvcp140.dll b/lib/msvcp140/x86/msvcp140.dll
new file mode 100644
index 0000000000..8501b34b3c
Binary files /dev/null and b/lib/msvcp140/x86/msvcp140.dll differ
diff --git a/lib/msvcp140/x86/ucrtbase.dll b/lib/msvcp140/x86/ucrtbase.dll
new file mode 100644
index 0000000000..92c99d0cab
Binary files /dev/null and b/lib/msvcp140/x86/ucrtbase.dll differ
diff --git a/lib/msvcp140/x86/vccorlib140.dll b/lib/msvcp140/x86/vccorlib140.dll
new file mode 100644
index 0000000000..48f5789c67
Binary files /dev/null and b/lib/msvcp140/x86/vccorlib140.dll differ
diff --git a/lib/msvcp140/x86/vcruntime140.dll b/lib/msvcp140/x86/vcruntime140.dll
new file mode 100644
index 0000000000..21c18f7e82
Binary files /dev/null and b/lib/msvcp140/x86/vcruntime140.dll differ
diff --git a/lib/nuget/nuget.exe b/lib/nuget/nuget.exe
new file mode 100644
index 0000000000..9f8781de0d
Binary files /dev/null and b/lib/nuget/nuget.exe differ
diff --git a/lib/sqlce/amd64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest b/lib/sqlce/x64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest
similarity index 100%
rename from lib/sqlce/amd64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest
rename to lib/sqlce/x64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest
diff --git a/lib/sqlce/amd64/Microsoft.VC90.CRT/README_ENU.txt b/lib/sqlce/x64/Microsoft.VC90.CRT/README_ENU.txt
similarity index 100%
rename from lib/sqlce/amd64/Microsoft.VC90.CRT/README_ENU.txt
rename to lib/sqlce/x64/Microsoft.VC90.CRT/README_ENU.txt
diff --git a/lib/sqlce/amd64/Microsoft.VC90.CRT/msvcr90.dll b/lib/sqlce/x64/Microsoft.VC90.CRT/msvcr90.dll
similarity index 100%
rename from lib/sqlce/amd64/Microsoft.VC90.CRT/msvcr90.dll
rename to lib/sqlce/x64/Microsoft.VC90.CRT/msvcr90.dll
diff --git a/lib/sqlce/amd64/sqlceca40.dll b/lib/sqlce/x64/sqlceca40.dll
similarity index 100%
rename from lib/sqlce/amd64/sqlceca40.dll
rename to lib/sqlce/x64/sqlceca40.dll
diff --git a/lib/sqlce/amd64/sqlcecompact40.dll b/lib/sqlce/x64/sqlcecompact40.dll
similarity index 100%
rename from lib/sqlce/amd64/sqlcecompact40.dll
rename to lib/sqlce/x64/sqlcecompact40.dll
diff --git a/lib/sqlce/amd64/sqlceer40EN.dll b/lib/sqlce/x64/sqlceer40EN.dll
similarity index 100%
rename from lib/sqlce/amd64/sqlceer40EN.dll
rename to lib/sqlce/x64/sqlceer40EN.dll
diff --git a/lib/sqlce/amd64/sqlceme40.dll b/lib/sqlce/x64/sqlceme40.dll
similarity index 100%
rename from lib/sqlce/amd64/sqlceme40.dll
rename to lib/sqlce/x64/sqlceme40.dll
diff --git a/lib/sqlce/amd64/sqlceqp40.dll b/lib/sqlce/x64/sqlceqp40.dll
similarity index 100%
rename from lib/sqlce/amd64/sqlceqp40.dll
rename to lib/sqlce/x64/sqlceqp40.dll
diff --git a/lib/sqlce/amd64/sqlcese40.dll b/lib/sqlce/x64/sqlcese40.dll
similarity index 100%
rename from lib/sqlce/amd64/sqlcese40.dll
rename to lib/sqlce/x64/sqlcese40.dll
diff --git a/lib/vswhere/vswhere.exe b/lib/vswhere/vswhere.exe
new file mode 100644
index 0000000000..3d91a17dc4
Binary files /dev/null and b/lib/vswhere/vswhere.exe differ
diff --git a/src/AssemblySharedInfo.cs b/src/AssemblySharedInfo.cs
index 79225de4c7..b58492422f 100644
--- a/src/AssemblySharedInfo.cs
+++ b/src/AssemblySharedInfo.cs
@@ -7,6 +7,6 @@
[assembly: AssemblyDescription("SmartStore.NET")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("SmartStore AG")]
-[assembly: AssemblyCopyright("Copyright © SmartStore AG 2016")]
+[assembly: AssemblyCopyright("Copyright © SmartStore AG 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs
index f74f0edf78..719cd1d6ff 100644
--- a/src/AssemblyVersionInfo.cs
+++ b/src/AssemblyVersionInfo.cs
@@ -9,7 +9,7 @@
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
-[assembly: AssemblyVersion("2.6.0.0")]
+[assembly: AssemblyVersion("3.2.2.0")]
-[assembly: AssemblyFileVersion("2.6.0.0")]
-[assembly: AssemblyInformationalVersion("2.6.0.0")]
+[assembly: AssemblyFileVersion("3.2.2.0")]
+[assembly: AssemblyInformationalVersion("3.2.2.0")]
diff --git a/src/Libraries/SmartStore.Core/Async/AsyncLock.cs b/src/Libraries/SmartStore.Core/Async/AsyncLock.cs
deleted file mode 100644
index b93da92afc..0000000000
--- a/src/Libraries/SmartStore.Core/Async/AsyncLock.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using SmartStore.Utilities;
-
-namespace SmartStore.Core.Async
-{
- public sealed class AsyncLock : DisposableObject
- {
- private static readonly TimeSpan _noTimeout = TimeSpan.FromMilliseconds(-1);
- private static readonly ConcurrentDictionary _keyLocks = new ConcurrentDictionary();
-
- private readonly SemaphoreSlim _semaphore;
- private readonly int _initialCount;
-
- public AsyncLock()
- : this(1, int.MaxValue)
- {
- }
-
- public AsyncLock(int initialCount)
- : this(initialCount, int.MaxValue)
- {
- }
-
- public AsyncLock(int initialCount, int maxCount)
- {
- _semaphore = new SemaphoreSlim(initialCount, maxCount);
- _initialCount = initialCount;
- }
-
- public static AsyncLock Acquire(object key)
- {
- Guard.NotNull(key, nameof(key));
-
- var asyncLock = _keyLocks.GetOrAdd(key, x => new AsyncLock());
-
- // Perf: once we have more than 100 lock objects in the storage, remove the idle ones.
- if (_keyLocks.Count > 100)
- {
- // never remove the current one and the locked ones
- var idles = _keyLocks.Where(kvp => kvp.Key != key && !kvp.Value.IsLockHeld).ToArray();
- foreach (var kvp in idles)
- {
- var value = kvp.Value;
- // double check for held lock
- if (!value.IsLockHeld)
- {
- _keyLocks.TryRemove(kvp.Key, out value);
- }
- }
- }
-
- return asyncLock;
- }
-
- public bool IsLockHeld
- {
- get { return _semaphore.CurrentCount < _initialCount; }
- }
-
- public int Release()
- {
- return _semaphore.Release();
- }
-
- public int Release(int releaseCount)
- {
- return _semaphore.Release(releaseCount);
- }
-
- public Task LockAsync()
- {
- return LockAsync(_noTimeout, CancellationToken.None);
- }
-
- public Task LockAsync(TimeSpan timeout)
- {
- return LockAsync(timeout, CancellationToken.None);
- }
-
- public Task LockAsync(CancellationToken cancellationToken)
- {
- return LockAsync(_noTimeout, cancellationToken);
- }
-
- public Task LockAsync(TimeSpan timeout, CancellationToken cancellationToken)
- {
- var wait = _semaphore.WaitAsync(timeout, cancellationToken);
-
- return wait.IsCompleted
- ? Task.FromResult(new ActionDisposable(() => _semaphore.Release()) as IDisposable)
- : wait.ContinueWith((t, s) => new ActionDisposable(() => ((SemaphoreSlim)s).Release()) as IDisposable,
- _semaphore,
- CancellationToken.None,
- TaskContinuationOptions.ExecuteSynchronously,
- TaskScheduler.Default);
- }
-
- protected override void OnDispose(bool disposing)
- {
- if (disposing)
- _semaphore.Dispose();
- }
- }
-}
diff --git a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
index 1fce2cc436..2751259eef 100644
--- a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
+++ b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -8,113 +9,166 @@
namespace SmartStore.Core.Async
{
- public static class AsyncRunner
+ using EventQueue = ConcurrentQueue>;
+
+ public class AsyncRunner : IDisposable
{
- private static readonly BackgroundWorkHost _host = new BackgroundWorkHost();
+ #region Instance members
- ///
- /// Gets the global cancellation token which signals the application shutdown
- ///
- public static CancellationToken AppShutdownCancellationToken
+ private ExclusiveSynchronizationContext CurrentContext;
+ private SynchronizationContext OldContext;
+ private int TaskCount;
+
+ private AsyncRunner()
{
- get { return _host.ShutdownCancellationTokenSource.Token; }
+ OldContext = SynchronizationContext.Current;
+ CurrentContext = new ExclusiveSynchronizationContext(OldContext);
+ SynchronizationContext.SetSynchronizationContext(CurrentContext);
+ }
+
+ public void Dispose()
+ {
+ try
+ {
+ CurrentContext.BeginMessageLoop();
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ finally
+ {
+ SynchronizationContext.SetSynchronizationContext(OldContext);
+ }
+ }
+
+ private void Increment()
+ {
+ Interlocked.Increment(ref TaskCount);
+ }
+
+ private void Decrement()
+ {
+ Interlocked.Decrement(ref TaskCount);
+ if (TaskCount == 0)
+ {
+ CurrentContext.EndMessageLoop();
+ }
}
///
/// Executes an async Task method which has a void return value synchronously
///
- /// Task method to execute
- public static void RunSync(Func func)
+ /// Task execute
+ public void Run(Task task, Action continuation = null)
{
- var oldContext = SynchronizationContext.Current;
- var synch = new ExclusiveSynchronizationContext();
- SynchronizationContext.SetSynchronizationContext(synch);
- synch.Post(async _ =>
+ CurrentContext.Post(async _ =>
{
try
{
- await func();
+ Increment();
+ await task;
+
+ continuation?.Invoke(task);
}
catch (Exception e)
{
- synch.InnerException = e;
- throw;
+ CurrentContext.InnerException = e;
}
finally
{
- synch.EndMessageLoop();
+ Decrement();
}
}, null);
- synch.BeginMessageLoop();
-
- SynchronizationContext.SetSynchronizationContext(oldContext);
}
///
/// Executes an async Task method which has a TResult return type synchronously
///
/// Return Type
- /// Task method to execute
- ///
- public static TResult RunSync(Func> func)
+ /// Task to execute
+ public TResult Run(Task task, Action> continuation = null)
{
- var oldContext = SynchronizationContext.Current;
- var synch = new ExclusiveSynchronizationContext();
- SynchronizationContext.SetSynchronizationContext(synch);
- TResult ret = default(TResult);
- synch.Post(async _ =>
+ var result = default(TResult);
+
+ CurrentContext.Post(async _ =>
{
try
{
- ret = await func();
+ Increment();
+ result = await task;
+
+ continuation?.Invoke(task);
}
catch (Exception e)
{
- synch.InnerException = e;
- throw;
+ CurrentContext.InnerException = e;
}
finally
{
- synch.EndMessageLoop();
+ Decrement();
}
}, null);
- synch.BeginMessageLoop();
- SynchronizationContext.SetSynchronizationContext(oldContext);
- return ret;
- }
- public static Task Run(Action action)
- {
- return Run(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
+ return result;
}
- public static Task Run(Action action, CancellationToken cancellationToken)
+ #endregion
+
+ private static readonly BackgroundWorkHost _host = new BackgroundWorkHost();
+
+ ///
+ /// Gets the global cancellation token which signals the application shutdown
+ ///
+ public static CancellationToken AppShutdownCancellationToken
{
- return Run(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
+ get { return _host.ShutdownCancellationTokenSource.Token; }
}
- public static Task Run(Action action, TaskCreationOptions options)
+ public static AsyncRunner Create()
{
- return Run(action, CancellationToken.None, options, TaskScheduler.Default);
+ return new AsyncRunner();
}
- public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options)
+ ///
+ /// Executes an async Task method which has a void return value synchronously
+ ///
+ /// Task method to execute
+ public static void RunSync(Func func, Action continuation = null)
{
- return Run(action, cancellationToken, options, TaskScheduler.Default);
+ using (var runner = new AsyncRunner())
+ {
+ runner.Run(func(), continuation);
+ }
}
- public static Task Run(Action action, TaskScheduler scheduler)
+ ///
+ /// Executes an async Task method which has a TResult return type synchronously
+ ///
+ /// Return Type
+ /// Task method to execute
+ ///
+ public static TResult RunSync(Func> func, Action> continuation = null)
{
- return Run(action, CancellationToken.None, TaskCreationOptions.None, scheduler);
+ var result = default(TResult);
+
+ using (var runner = new AsyncRunner())
+ {
+ result = runner.Run(func(), continuation);
+ }
+
+ return result;
}
- public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
+ public static Task Run(
+ Action action,
+ CancellationToken cancellationToken = default(CancellationToken),
+ TaskCreationOptions options = TaskCreationOptions.LongRunning,
+ TaskScheduler scheduler = null)
{
Guard.NotNull(action, nameof(action));
- Guard.NotNull(scheduler, nameof(scheduler));
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
- options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
var t = Task.Factory.StartNew(() => {
var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
@@ -122,21 +176,24 @@ public static Task Run(Action action, Cancell
{
action(accessor.GetLifetimeScope(null), ct);
}
- }, ct, options, scheduler);
+ }, ct, options, scheduler ?? TaskScheduler.Default);
_host.Register(t, ct);
return t;
}
- public static Task Run(Action action, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
+ public static Task Run(
+ Action action,
+ object state,
+ CancellationToken cancellationToken = default(CancellationToken),
+ TaskCreationOptions options = TaskCreationOptions.LongRunning,
+ TaskScheduler scheduler = null)
{
Guard.NotNull(state, nameof(state));
Guard.NotNull(action, nameof(action));
- Guard.NotNull(scheduler, nameof(scheduler));
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
- options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
var t = Task.Factory.StartNew((o) =>
{
@@ -145,47 +202,22 @@ public static Task Run(Action action,
{
action(accessor.GetLifetimeScope(null), ct, o);
}
- }, state, ct, options, scheduler);
+ }, state, ct, options, scheduler ?? TaskScheduler.Default);
_host.Register(t, ct);
return t;
}
-
-
- public static Task Run(Func function)
- {
- return Run(function, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
- }
-
- public static Task Run(Func function, CancellationToken cancellationToken)
- {
- return Run(function, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
- }
-
- public static Task Run(Func function, TaskCreationOptions options)
- {
- return Run(function, CancellationToken.None, options, TaskScheduler.Default);
- }
-
- public static Task Run(Func function, CancellationToken cancellationToken, TaskCreationOptions options)
- {
- return Run(function, cancellationToken, options, TaskScheduler.Default);
- }
-
- public static Task Run(Func function, TaskScheduler scheduler)
- {
- return Run(function, CancellationToken.None, TaskCreationOptions.None, scheduler);
- }
-
- public static Task Run(Func function, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
+ public static Task Run(
+ Func function,
+ CancellationToken cancellationToken = default(CancellationToken),
+ TaskCreationOptions options = TaskCreationOptions.LongRunning,
+ TaskScheduler scheduler = null)
{
Guard.NotNull(function, nameof(function));
- Guard.NotNull(scheduler, nameof(scheduler));
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
- options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
var t = Task.Factory.StartNew(() =>
{
@@ -194,21 +226,24 @@ public static Task Run(Func Run(Func function, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
+ public static Task Run(
+ Func function,
+ object state,
+ CancellationToken cancellationToken = default(CancellationToken),
+ TaskCreationOptions options = TaskCreationOptions.LongRunning,
+ TaskScheduler scheduler = null)
{
Guard.NotNull(state, nameof(state));
Guard.NotNull(function, nameof(function));
- Guard.NotNull(scheduler, nameof(scheduler));
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
- options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
var t = Task.Factory.StartNew((o) =>
{
@@ -217,19 +252,61 @@ public static Task Run(Func function,
+ object state,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Guard.NotNull(state, nameof(state));
+ Guard.NotNull(function, nameof(function));
+
+ var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
+
+ var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
+ var scope = accessor.BeginLifetimeScope(null);
+
+ Task task = null;
+
+ try
+ {
+ task = function(scope, ct).ContinueWith(x =>
+ {
+ scope.Dispose();
+ });
+ _host.Register(task, ct);
+ }
+ catch
+ {
+ scope.Dispose();
+ }
+
+ return task;
+ }
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool _done;
- readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false);
- readonly Queue> _items = new Queue>();
+ private readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false);
+ private readonly EventQueue _items;
+
+ public ExclusiveSynchronizationContext(SynchronizationContext old)
+ {
+ if (old is ExclusiveSynchronizationContext oldEx)
+ {
+ this._items = oldEx._items;
+ }
+ else
+ {
+ this._items = new EventQueue();
+ }
+ }
public Exception InnerException { get; set; }
@@ -240,10 +317,7 @@ public override void Send(SendOrPostCallback d, object state)
public override void Post(SendOrPostCallback d, object state)
{
- lock (_items)
- {
- _items.Enqueue(Tuple.Create(d, state));
- }
+ _items.Enqueue(Tuple.Create(d, state));
_workItemsWaiting.Set();
}
@@ -256,26 +330,19 @@ public void BeginMessageLoop()
{
while (!_done)
{
- Tuple task = null;
- lock (_items)
+ if (!_items.TryDequeue(out var task))
{
- if (_items.Count > 0)
- {
- task = _items.Dequeue();
- }
+ _workItemsWaiting.WaitOne();
}
- if (task != null)
+ else
{
task.Item1(task.Item2);
- if (InnerException != null) // the method threw an exeption
+
+ if (InnerException != null) // method threw an exeption
{
throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
}
}
- else
- {
- _workItemsWaiting.WaitOne();
- }
}
}
@@ -291,7 +358,7 @@ internal class BackgroundWorkHost : IRegisteredObject
{
private readonly CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource();
private int _numRunningWorkItems;
-
+
public BackgroundWorkHost()
{
HostingEnvironment.RegisterObject(this);
@@ -337,7 +404,7 @@ public void Register(Task work, CancellationToken cancellationToken)
}
_numRunningWorkItems++;
}
-
+
work.ContinueWith(
WorkItemComplete,
CancellationToken.None,
@@ -353,7 +420,7 @@ private void WorkItemComplete(Task work)
lock (this)
{
num = --_numRunningWorkItems;
- isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested;
+ isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested;
}
if (num == 0 && isCancellationRequested)
{
diff --git a/src/Libraries/SmartStore.Core/Async/AsyncState.cs b/src/Libraries/SmartStore.Core/Async/AsyncState.cs
deleted file mode 100644
index 67326de5d5..0000000000
--- a/src/Libraries/SmartStore.Core/Async/AsyncState.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.Caching;
-using System.Threading;
-
-namespace SmartStore.Core.Async
-{
- public class AsyncState
- {
- private static readonly AsyncState s_instance = new AsyncState();
- private readonly MemoryCache _cache = new MemoryCache("SmartStore.AsyncState");
-
- private AsyncState()
- {
- }
-
- public static AsyncState Current
- {
- get { return s_instance; }
- }
-
- public bool Exists(string name = null)
- {
- var key = BuildKey(name);
- return _cache.Contains(key) && !object.Equals(((StateInfo)_cache[key]).Progress, default(T));
- }
-
- public T Get(string name = null)
- {
- CancellationTokenSource cancelTokenSource;
- return Get(out cancelTokenSource, name);
- }
-
- public IEnumerable GetAll()
- {
- var keyPrefix = BuildKey(null);
- foreach (var kvp in _cache)
- {
- if (kvp.Key.StartsWith(keyPrefix))
- {
- var value = kvp.Value as StateInfo;
- if (value != null && value.Progress != null)
- {
- yield return (T)value.Progress;
- }
- }
- }
- }
-
- public CancellationTokenSource GetCancelTokenSource(string name = null)
- {
- CancellationTokenSource cancelTokenSource;
- Get(out cancelTokenSource, name);
- return cancelTokenSource;
- }
-
- private T Get(out CancellationTokenSource cancelTokenSource, string name = null)
- {
- cancelTokenSource = null;
- var key = BuildKey(name);
-
- var value = _cache.Get(key) as StateInfo;
-
- if (value != null)
- {
- cancelTokenSource = value.CancellationTokenSource;
- return (T)value.Progress;
- }
-
- return default(T);
- }
-
- public void Set(T state, string name = null, bool neverExpires = false)
- {
- Guard.NotNull(state, nameof(state));
- Set(state, null, name, neverExpires);
- }
-
- public void Update(Action update, string name = null)
- {
- Guard.NotNull(update, nameof(update));
-
- var key = BuildKey(typeof(T), name);
-
- var value = _cache.Get(key) as StateInfo;
- if (value != null)
- {
- var state = (T)value.Progress;
- if (state != null)
- {
- update(state);
- }
- }
- }
-
- public void SetCancelTokenSource(CancellationTokenSource cancelTokenSource, string name = null)
- {
- Guard.NotNull(cancelTokenSource, nameof(cancelTokenSource));
-
- Set(default(T), cancelTokenSource, name);
- }
-
- private void Set(T state, CancellationTokenSource cancelTokenSource, string name = null, bool neverExpires = false)
- {
- var key = BuildKey(typeof(T), name);
-
- var value = _cache.Get(key) as StateInfo;
-
- if (value != null)
- {
- // exists already, so update
- if (state != null)
- {
- value.Progress = state;
- }
- if (cancelTokenSource != null && value.CancellationTokenSource == null)
- {
- value.CancellationTokenSource = cancelTokenSource;
- }
- }
-
- var policy = new CacheItemPolicy { SlidingExpiration = neverExpires ? TimeSpan.Zero : TimeSpan.FromMinutes(15) };
-
- _cache.Set(
- key,
- value ?? new StateInfo { Progress = state, CancellationTokenSource = cancelTokenSource },
- policy);
- }
-
- public bool Remove(string name = null)
- {
- var key = BuildKey(name);
- var value = _cache.Remove(key) as StateInfo;
-
- if (value != null)
- {
- if (value.CancellationTokenSource != null)
- {
- value.CancellationTokenSource.Dispose();
- }
- return true;
- }
-
- return false;
- }
-
- private string BuildKey(string name)
- {
- return BuildKey(typeof(T), name);
- }
-
- private string BuildKey(Type type, string name)
- {
- return "{0}:{1}:{2}".FormatInvariant("AsyncStateKey", type.FullName, name.EmptyNull());
- }
-
- class StateInfo
- {
- public object Progress
- {
- get;
- set;
- }
-
- public CancellationTokenSource CancellationTokenSource
- {
- get;
- set;
- }
- }
- }
-}
diff --git a/src/Libraries/SmartStore.Core/Async/AsyncStateInfo.cs b/src/Libraries/SmartStore.Core/Async/AsyncStateInfo.cs
new file mode 100644
index 0000000000..8bc3898736
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Async/AsyncStateInfo.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace SmartStore.Core.Async
+{
+ [Serializable]
+ public class AsyncStateInfo
+ {
+ // used for serialization compatibility
+ public static readonly string Version = "1";
+
+ public object Progress
+ {
+ get;
+ set;
+ }
+
+ public DateTime CreatedOnUtc
+ {
+ get;
+ set;
+ }
+
+ public DateTime LastAccessUtc
+ {
+ get;
+ set;
+ }
+
+ public TimeSpan Duration
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Async/IAsyncState.cs b/src/Libraries/SmartStore.Core/Async/IAsyncState.cs
new file mode 100644
index 0000000000..bbfa0d3266
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Async/IAsyncState.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SmartStore.Core.Async
+{
+ public interface IAsyncState
+ {
+ bool Exists(string name = null);
+
+ T Get(string name = null);
+ IEnumerable GetAll();
+
+ void Set(T state, string name = null, bool neverExpires = false);
+ void Update(Action update, string name = null);
+ bool Remove(string name = null);
+
+ CancellationTokenSource GetCancelTokenSource(string name = null);
+ void SetCancelTokenSource(CancellationTokenSource cancelTokenSource, string name = null);
+ bool Cancel(string name = null);
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs b/src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs
new file mode 100644
index 0000000000..a8fa0ceb0f
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Caching;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SmartStore.Core.Async
+{
+ public partial class LocalAsyncState : IAsyncState
+ {
+ private readonly MemoryCache _states = new MemoryCache("SmartStore.AsyncState.Progress");
+ private readonly MemoryCache _cancelTokens = new MemoryCache("SmartStore.AsyncState.CancelTokenSources");
+
+ public virtual bool Exists(string name = null)
+ {
+ var value = GetStateInfo(name);
+ return value != null && !object.Equals(value.Progress, default(T));
+ }
+
+ public virtual T Get(string name = null)
+ {
+ var value = GetStateInfo(name);
+
+ if (value != null)
+ {
+ return (T)value.Progress;
+ }
+
+ return default(T);
+ }
+
+ public virtual IEnumerable GetAll()
+ {
+ var keyPrefix = BuildKey(null);
+ return _states
+ .Where(x => x.Key.StartsWith(keyPrefix))
+ .Select(x => x.Value)
+ .OfType()
+ .Select(x => x.Progress)
+ .OfType();
+ }
+
+
+ public virtual void Set(T state, string name = null, bool neverExpires = false)
+ {
+ Guard.NotNull(state, nameof(state));
+
+ var value = GetStateInfo(name);
+
+ if (value != null)
+ {
+ // exists already, so update
+ if (state != null)
+ {
+ value.Progress = state;
+ }
+ }
+ else
+ {
+ // add new entry
+ var duration = neverExpires ? TimeSpan.Zero : TimeSpan.FromMinutes(15);
+ var policy = new CacheItemPolicy { SlidingExpiration = duration };
+ var key = BuildKey(name);
+
+ // On expiration or removal: remove corresponding cancel token also.
+ policy.RemovedCallback = (x) => OnRemoveCancelTokenSource(key);
+
+ _states.Set(key, new AsyncStateInfo { Progress = state, Duration = duration }, policy);
+ }
+ }
+
+ public virtual void Update(Action update, string name = null)
+ {
+ Guard.NotNull(update, nameof(update));
+
+ var value = GetStateInfo(name);
+
+ if (value != null)
+ {
+ var state = (T)value.Progress;
+ if (state != null)
+ {
+ update(state);
+ }
+ }
+ }
+
+ public virtual bool Remove(string name = null)
+ {
+ var key = BuildKey(name);
+
+ if (OnRemoveStateInfo(key))
+ {
+ OnRemoveCancelTokenSource(key);
+ return true;
+ }
+
+ return false;
+ }
+
+ protected virtual bool OnRemoveStateInfo(string key)
+ {
+ return _states.Remove(key) != null;
+ }
+
+ protected virtual bool OnRemoveCancelTokenSource(string key, bool successive = false)
+ {
+ Guard.NotEmpty(key, nameof(key));
+
+ var token = _cancelTokens.Remove(key) as CancellationTokenSource;
+
+ if (token != null)
+ {
+ token.Dispose();
+ return true;
+ }
+
+ return false;
+ }
+
+
+ public CancellationTokenSource GetCancelTokenSource(string name = null)
+ {
+ return OnGetCancelTokenSource(BuildKey(name));
+ }
+
+ protected virtual CancellationTokenSource OnGetCancelTokenSource(string key, bool successive = false)
+ {
+ Guard.NotEmpty(key, nameof(key));
+
+ var token = _cancelTokens.Get(key);
+
+ if (token != null)
+ {
+ return (CancellationTokenSource)token;
+ }
+
+ return null;
+ }
+
+ public virtual void SetCancelTokenSource(CancellationTokenSource cancelTokenSource, string name = null)
+ {
+ Guard.NotNull(cancelTokenSource, nameof(cancelTokenSource));
+
+ var key = BuildKey(name);
+
+ if (Exists(name))
+ {
+ OnRemoveCancelTokenSource(key);
+ }
+
+ var policy = new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(15) };
+
+ _cancelTokens.Set(key, cancelTokenSource, policy);
+ }
+
+ public bool Cancel(string name = null)
+ {
+ return OnCancel(BuildKey(name));
+ }
+
+ protected virtual bool OnCancel(string key, bool successive = false)
+ {
+ Guard.NotEmpty(key, nameof(key));
+
+ var cts = OnGetCancelTokenSource(key);
+
+ if (cts != null)
+ {
+ cts.Cancel();
+ return true;
+ }
+
+ return false;
+ }
+
+
+ protected virtual AsyncStateInfo GetStateInfo(string name = null)
+ {
+ var key = BuildKey(name);
+ var value = _states.Get(key) as AsyncStateInfo;
+
+ if (value != null)
+ {
+ // ensures that sliding expiration gets updated on cancel token
+ _cancelTokens.Get(key);
+ }
+
+ return value;
+ }
+
+ protected string BuildKey(string name)
+ {
+ return BuildKey(typeof(T), name);
+ }
+
+ protected virtual string BuildKey(Type type, string name)
+ {
+ return "{0}{1}".FormatInvariant(type.FullName, name.HasValue() ? ":" + name : "");
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/BaseEntity.cs b/src/Libraries/SmartStore.Core/BaseEntity.cs
index 0c90dc9a9a..3fd0016909 100644
--- a/src/Libraries/SmartStore.Core/BaseEntity.cs
+++ b/src/Libraries/SmartStore.Core/BaseEntity.cs
@@ -1,18 +1,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Data.Entity.Core.Objects;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
namespace SmartStore.Core
-{
-
+{
///
/// Base class for entities
///
[DataContract]
- public abstract partial class BaseEntity
+ public abstract partial class BaseEntity : IEquatable
{
///
/// Gets or sets the entity identifier
@@ -21,20 +21,30 @@ public abstract partial class BaseEntity
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
- [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ public virtual string GetEntityName()
+ {
+ return GetUnproxiedType().Name;
+ }
+
public Type GetUnproxiedType()
{
- var t = GetType();
- if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity."))
- {
- // it's a proxied type
- t = t.BaseType;
- }
- return t;
- }
+ #region Old
+ //var t = GetType();
+ //if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity."))
+ //{
+ // // it's a proxied type
+ // t = t.BaseType;
+ //}
+
+ //return t;
+ #endregion
+
+ return ObjectContext.GetObjectType(GetType());
+ }
///
- /// Transient objects are not associated with an item already in storage. For instance,
+ /// Transient objects are not associated with an item already in storage. For instance,
/// a Product entity is transient if its Id is 0.
///
public virtual bool IsTransientRecord()
@@ -44,28 +54,33 @@ public virtual bool IsTransientRecord()
public override bool Equals(object obj)
{
- return Equals(obj as BaseEntity);
+ return this.Equals(obj as BaseEntity);
}
- protected virtual bool Equals(BaseEntity other)
- {
- if (other == null)
- return false;
+ bool IEquatable.Equals(BaseEntity other)
+ {
+ return this.Equals(other);
+ }
- if (ReferenceEquals(this, other))
- return true;
+ protected virtual bool Equals(BaseEntity other)
+ {
+ if (other == null)
+ return false;
- if (HasSameNonDefaultIds(other))
- {
- var otherType = other.GetUnproxiedType();
- var thisType = GetUnproxiedType();
- return thisType.Equals(otherType);
- }
+ if (ReferenceEquals(this, other))
+ return true;
- return false;
- }
+ if (HasSameNonDefaultIds(other))
+ {
+ var otherType = other.GetUnproxiedType();
+ var thisType = GetUnproxiedType();
+ return thisType.Equals(otherType);
+ }
+
+ return false;
+ }
- [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
+ [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
public override int GetHashCode()
{
if (IsTransientRecord())
@@ -99,5 +114,5 @@ private bool HasSameNonDefaultIds(BaseEntity other)
{
return !this.IsTransientRecord() && !other.IsTransientRecord() && this.Id == other.Id;
}
- }
+ }
}
diff --git a/src/Libraries/SmartStore.Core/Caching/CacheScope.cs b/src/Libraries/SmartStore.Core/Caching/CacheScope.cs
new file mode 100644
index 0000000000..b705ab10b6
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/CacheScope.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SmartStore.Core.Caching
+{
+ public class CacheScope
+ {
+ private HashSet _dependencies;
+
+ public CacheScope(string key)
+ {
+ Key = key;
+ }
+
+ public string Key { get; private set; }
+
+ public void AddDependency(string key)
+ {
+ if (key == null)
+ {
+ return;
+ }
+
+ if (_dependencies == null)
+ {
+ _dependencies = new HashSet();
+ }
+
+ _dependencies.Add(key);
+ }
+
+ public IEnumerable Dependencies
+ {
+ get
+ {
+ return _dependencies ?? Enumerable.Empty();
+ }
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/CacheScopeAccessor.cs b/src/Libraries/SmartStore.Core/Caching/CacheScopeAccessor.cs
new file mode 100644
index 0000000000..b04e0a7d45
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/CacheScopeAccessor.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using SmartStore.Utilities;
+
+namespace SmartStore.Core.Caching
+{
+ public class CacheScopeAccessor : ICacheScopeAccessor
+ {
+ private readonly Stack _stack = new Stack();
+
+ public CacheScopeAccessor()
+ {
+ }
+
+ public CacheScope Current
+ {
+ get
+ {
+ _stack.TryPeek(out var result);
+ return result;
+ }
+ }
+
+ public bool HasScope(string key)
+ {
+ return _stack.Any(x => x.Key.IsCaseInsensitiveEqual(key));
+ }
+
+ public void PropagateKey(string key)
+ {
+ var current = Current;
+ if (current != null)
+ {
+ current.AddDependency(key);
+ }
+ }
+
+ public IDisposable BeginScope(string key, bool independent = false)
+ {
+ if (Current?.Key == key)
+ {
+ // Void if parent scope has the same key.
+ return ActionDisposable.Empty;
+ }
+
+ _stack.Push(new CacheScope(key));
+
+ var action = new ActionDisposable(() =>
+ {
+ _stack.TryPop(out _);
+
+ if (!independent)
+ {
+ PropagateKey(key);
+ }
+ });
+
+ return action;
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/DisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/DisplayControl.cs
deleted file mode 100644
index b39d25592f..0000000000
--- a/src/Libraries/SmartStore.Core/Caching/DisplayControl.cs
+++ /dev/null
@@ -1,221 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using SmartStore.Core.Domain.Blogs;
-using SmartStore.Core.Domain.Catalog;
-using SmartStore.Core.Domain.Discounts;
-using SmartStore.Core.Domain.News;
-using SmartStore.Core.Domain.Topics;
-
-namespace SmartStore.Core.Caching
-{
- public partial class DisplayControl : IDisplayControl
- {
- public static readonly HashSet CandidateTypes = new HashSet(new Type[]
- {
- typeof(BlogComment),
- typeof(BlogPost),
- typeof(Category),
- typeof(Manufacturer),
- typeof(Product),
- typeof(ProductBundleItem),
- typeof(ProductPicture),
- typeof(ProductSpecificationAttribute),
- typeof(SpecificationAttributeOption),
- typeof(ProductVariantAttribute),
- typeof(ProductVariantAttributeValue),
- typeof(ProductVariantAttributeCombination),
- typeof(TierPrice),
- typeof(Discount),
- typeof(CrossSellProduct),
- typeof(RelatedProduct),
- typeof(ProductCategory),
- typeof(ProductManufacturer),
- typeof(NewsItem),
- typeof(NewsComment),
- typeof(Topic)
- });
-
- private readonly HashSet _entities = new HashSet();
-
- private bool? _isUncacheableRequest;
-
- public void Announce(BaseEntity entity)
- {
- if (entity != null)
- {
- _entities.Add(entity);
- }
- }
-
- public bool IsDisplayed(BaseEntity entity)
- {
- if (entity == null)
- return false;
-
- return _entities.Contains(entity);
- }
-
- public void MarkRequestAsUncacheable()
- {
- // First wins: subsequent calls should not be able to cancel this
- _isUncacheableRequest = true;
- }
-
- public bool IsUncacheableRequest
- {
- get
- {
- return _isUncacheableRequest.GetValueOrDefault() == true;
- }
- }
-
- public IEnumerable GetCacheControlTagsFor(BaseEntity entity)
- {
- Guard.NotNull(entity, nameof(entity));
-
- // TODO: purge all products when: DeliveryTime, QuantityUnit, MeasureWeight change
-
- if (entity.IsTransientRecord())
- {
- yield break;
- }
-
- var type = entity.GetUnproxiedType();
-
- if (!CandidateTypes.Contains(type))
- {
- yield break;
- }
-
- if (type == typeof(BlogComment))
- {
- yield return "b" + ((BlogComment)entity).BlogPostId;
- }
- else if (type == typeof(BlogPost))
- {
- yield return "b" + entity.Id;
- }
- else if (type == typeof(Category))
- {
- yield return "c" + entity.Id;
- }
- else if (type == typeof(Manufacturer))
- {
- yield return "m" + entity.Id;
- }
- else if (type == typeof(Product))
- {
- var product = ((Product)entity);
- yield return "p" + entity.Id;
- if (product.ProductType == ProductType.GroupedProduct && product.ParentGroupedProductId > 0)
- {
- yield return "p" + product.ParentGroupedProductId;
- }
- }
- else if (type == typeof(ProductBundleItem))
- {
- yield return "p" + ((ProductBundleItem)entity).ProductId;
- }
- else if (type == typeof(ProductPicture))
- {
- yield return "p" + ((ProductPicture)entity).ProductId;
- }
- else if (type == typeof(ProductSpecificationAttribute))
- {
- yield return "p" + ((ProductSpecificationAttribute)entity).ProductId;
- }
- else if (type == typeof(SpecificationAttributeOption))
- {
- foreach (var attr in ((SpecificationAttributeOption)entity).ProductSpecificationAttributes)
- {
- yield return "p" + attr.ProductId;
- }
- }
- else if (type == typeof(ProductVariantAttribute))
- {
- yield return "p" + ((ProductVariantAttribute)entity).ProductId;
- }
- else if (type == typeof(ProductVariantAttributeValue))
- {
- yield return "p" + ((ProductVariantAttributeValue)entity).ProductVariantAttribute.ProductId;
- }
- else if (type == typeof(ProductVariantAttributeCombination))
- {
- yield return "p" + ((ProductVariantAttributeCombination)entity).ProductId;
- }
- else if (type == typeof(TierPrice))
- {
- yield return "p" + ((TierPrice)entity).ProductId;
- }
- else if (type == typeof(Discount))
- {
- var discount = (Discount)entity;
- if (discount.DiscountType == DiscountType.AssignedToCategories)
- {
- foreach (var category in discount.AppliedToCategories)
- {
- yield return "c" + category.Id;
- }
- }
- else if (discount.DiscountType == DiscountType.AssignedToSkus)
- {
- foreach (var product in discount.AppliedToProducts)
- {
- yield return "p" + product.Id;
- }
- }
- }
- else if (type == typeof(CrossSellProduct))
- {
- yield return "p" + ((CrossSellProduct)entity).ProductId1;
- yield return "p" + ((CrossSellProduct)entity).ProductId2;
- }
- else if (type == typeof(RelatedProduct))
- {
- yield return "p" + ((RelatedProduct)entity).ProductId1;
- yield return "p" + ((RelatedProduct)entity).ProductId2;
- }
- else if (type == typeof(ProductCategory))
- {
- yield return "c" + ((ProductCategory)entity).CategoryId;
- yield return "p" + ((ProductCategory)entity).ProductId;
- }
- else if (type == typeof(ProductManufacturer))
- {
- yield return "m" + ((ProductManufacturer)entity).ManufacturerId;
- yield return "p" + ((ProductManufacturer)entity).ProductId;
- }
- else if (type == typeof(NewsItem))
- {
- yield return "n" + entity.Id;
- }
- else if (type == typeof(NewsComment))
- {
- yield return "n" + ((NewsComment)entity).NewsItemId;
- }
- else if (type == typeof(Topic))
- {
- var topic = ((Topic)entity);
- if (!topic.RenderAsWidget)
- {
- yield return "t" + topic.Id;
- }
- }
- }
-
- public IEnumerable GetAllCacheControlTags()
- {
- var tags = _entities
- .Where(x => x.Id > 0)
- .SelectMany(x => GetCacheControlTagsFor(x))
- .Where(x => x != null)
- .Distinct()
- .ToArray();
-
- return tags;
- }
- }
-}
diff --git a/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs b/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs
index 3230315b07..855349106f 100644
--- a/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs
+++ b/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs
@@ -14,8 +14,9 @@ public interface ICacheManager
///
/// The type of the item to get
/// The cache item key
+ /// When true , no attemp will be made to invalidate depending/parent cache entries.
/// Cached item value or null if item with specified key does not exist in the cache
- T Get(string key);
+ T Get(string key, bool independent = false);
///
/// Gets a cache item associated with the specified key or adds the item
@@ -25,8 +26,9 @@ public interface ICacheManager
/// The cache item key
/// Func which returns value to be added to the cache
/// Absolute expiration time
+ /// When true , no attemp will be made to invalidate depending/parent cache entries.
/// Cached item value
- T Get(string key, Func acquirer, TimeSpan? duration = null);
+ T Get(string key, Func acquirer, TimeSpan? duration = null, bool independent = false);
///
/// Gets a cache item associated with the specified key or - if it doesn't exist in the cache -
@@ -36,8 +38,18 @@ public interface ICacheManager
/// The cache item key
/// Func which returns value to be added to the cache
/// Absolute expiration time
+ /// When true , no attemp will be made to invalidate depending/parent cache entries.
/// Cached item value
- Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null);
+ Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false);
+
+ ///
+ /// Gets a hashset associated with the specified key or. If key does not exist,
+ /// a new set is created and put to cache automatically
+ ///
+ /// The set cache item key
+ /// Optional acquirer callback that is invoked when requested set does not exist yet.
+ /// Cached item value
+ ISet GetHashSet(string key, Func> acquirer = null);
///
/// Adds a cache item with the specified key
@@ -45,7 +57,10 @@ public interface ICacheManager
/// Key
/// Value
/// Absolute expiration time
- void Set(string key, object value, TimeSpan? duration = null);
+ ///
+ /// A list of keys for cache entries that should be removed from the cache whenever this entry changes.
+ ///
+ void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null);
///
/// Gets a value indicating whether the value associated with the specified key is cached
@@ -61,17 +76,34 @@ public interface ICacheManager
void Remove(string key);
///
- /// Scans for all all keys in the underlying cache
+ /// Scans for all keys matching the input pattern
///
/// A key pattern. Can be null .
- /// The sequence of matching keys
- string[] Keys(string pattern);
+ /// An array of matching key names
+ ///
+ /// Supported glob-style patterns:
+ /// - h?llo matches hello, hallo and hxllo
+ /// - h*llo matches hllo and heeeello
+ /// - h[ae]llo matches hello and hallo, but not hillo
+ /// - h[^e]llo matches hallo, hbllo, ... but not hello
+ /// - h[a-b]llo matches hallo and hbllo
+ ///
+ IEnumerable Keys(string pattern);
///
- /// Removes items by pattern
+ /// Removes all entries with keys matching the input pattern
///
- /// pattern
- void RemoveByPattern(string pattern);
+ /// Glob pattern
+ /// Count of removed cache items
+ ///
+ /// Supported glob-style patterns:
+ /// - h?llo matches hello, hallo and hxllo
+ /// - h*llo matches hllo and heeeello
+ /// - h[ae]llo matches hello and hallo, but not hillo
+ /// - h[^e]llo matches hallo, hbllo, ... but not hello
+ /// - h[a-b]llo matches hallo and hbllo
+ ///
+ int RemoveByPattern(string pattern);
///
/// Clear all cache data
diff --git a/src/Libraries/SmartStore.Core/Caching/ICacheScopeAccessor.cs b/src/Libraries/SmartStore.Core/Caching/ICacheScopeAccessor.cs
new file mode 100644
index 0000000000..a640e6b200
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/ICacheScopeAccessor.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace SmartStore.Core.Caching
+{
+ public interface ICacheScopeAccessor
+ {
+ ///
+ /// Returns the current cache acquire scope (for current thread)
+ ///
+ CacheScope Current { get; }
+
+ ///
+ /// Whether any scope with the passed exists already. Used to prevent lock recursions.
+ ///
+ /// Key
+ bool HasScope(string key);
+
+ ///
+ /// Notifies the current scope about a cache dependency.
+ ///
+ ///
+ void PropagateKey(string key);
+
+ ///
+ /// Begins a new cache acquire scope.
+ ///
+ /// Key
+ ///
+ /// true means that no key propagation will be performed, making the current cache entry independent from its parent.
+ /// Meaning: parent cache entries will not be auto-invalidated when this entry changes.
+ ///
+ IDisposable BeginScope(string key, bool independent = false);
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs b/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs
index 4815a8aafb..2ecd14f0b8 100644
--- a/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs
@@ -31,7 +31,7 @@ public interface IRequestCache
///
/// Key
/// Value
- void Set(string key, object value);
+ void Put(string key, object value);
///
/// Gets a value indicating whether the value associated with the specified key is cached
@@ -101,7 +101,7 @@ public void RemoveByPattern(string pattern)
{
}
- public void Set(string key, object value)
+ public void Put(string key, object value)
{
}
}
diff --git a/src/Libraries/SmartStore.Core/Caching/ISet.cs b/src/Libraries/SmartStore.Core/Caching/ISet.cs
new file mode 100644
index 0000000000..d0bff72bd7
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/ISet.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+namespace SmartStore.Core.Caching
+{
+ public interface ISet : IEnumerable
+ {
+ bool Add(string item);
+ void AddRange(IEnumerable items);
+ void Clear();
+ bool Contains(string item);
+ bool Remove(string item);
+ bool Move(string destinationKey, string item);
+
+ long UnionWith(params string[] keys);
+ long IntersectWith(params string[] keys);
+ long ExceptWith(params string[] keys);
+
+ int Count { get; }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs b/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs
index 8d29d59168..a00224784d 100644
--- a/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs
+++ b/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs
@@ -4,23 +4,37 @@
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Threading;
-using SmartStore.Core.Async;
+using System.Collections.Generic;
+using SmartStore.Utilities;
+using System.Text.RegularExpressions;
+using System.Diagnostics;
+using SmartStore.Utilities.Threading;
+using SmartStore.Core.Infrastructure.DependencyManagement;
namespace SmartStore.Core.Caching
{
public partial class MemoryCacheManager : DisposableObject, ICacheManager
{
+ const string LockRecursionExceptionMessage = "Acquiring identical cache items recursively is not supported. Key: {0}";
+
// Wwe put a special string into cache if value is null,
// otherwise our 'Contains()' would always return false,
// which is bad if we intentionally wanted to save NULL values.
public const string FakeNull = "__[NULL]__";
- private readonly MemoryCache _cache;
+ private readonly Work _scopeAccessor;
+ private MemoryCache _cache;
private readonly ConcurrentDictionary _keyLocks = new ConcurrentDictionary();
- public MemoryCacheManager()
+ public MemoryCacheManager(Work scopeAccessor)
{
- _cache = new MemoryCache("SmartStore");
+ _scopeAccessor = scopeAccessor;
+ _cache = CreateCache();
+ }
+
+ private MemoryCache CreateCache()
+ {
+ return new MemoryCache("SmartStore");
}
public bool IsDistributedCache
@@ -28,7 +42,7 @@ public bool IsDistributedCache
get { return false; }
}
- private bool TryGet(string key, out T value)
+ private bool TryGet(string key, bool independent, out T value)
{
value = default(T);
@@ -36,6 +50,12 @@ private bool TryGet(string key, out T value)
if (obj != null)
{
+ // Make the parent scope's entry depend on this
+ if (!independent)
+ {
+ _scopeAccessor.Value.PropagateKey(key);
+ }
+
if (obj.Equals(FakeNull))
{
return true;
@@ -48,73 +68,74 @@ private bool TryGet(string key, out T value)
return false;
}
- public T Get(string key)
+ public T Get(string key, bool independent = false)
{
- T value;
- TryGet(key, out value);
-
+ TryGet(key, independent, out T value);
return value;
}
- public T Get(string key, Func acquirer, TimeSpan? duration = null)
+ public T Get(string key, Func acquirer, TimeSpan? duration = null, bool independent = false)
{
- T value;
-
- if (TryGet(key, out value))
+ if (TryGet(key, independent, out T value))
{
return value;
}
- lock (String.Intern(key))
+ if (_scopeAccessor.Value.HasScope(key))
{
- // atomic operation must be outer locked
- if (!TryGet(key, out value))
+ throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
+ }
+
+ // Get the (semaphore) locker specific to this key
+ using (KeyedLock.Lock("cache:" + key, TimeSpan.FromMinutes(1)))
+ {
+ // Atomic operation must be outer locked
+ if (!TryGet(key, independent, out value))
{
- value = acquirer();
- Set(key, value, duration);
- return value;
+ using (_scopeAccessor.Value.BeginScope(key))
+ {
+ value = acquirer();
+ Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies);
+ return value;
+ }
}
}
return value;
}
- public async Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null)
+ public async Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false)
{
- T value;
-
- if (TryGet(key, out value))
+ if (TryGet(key, independent, out T value))
{
return value;
}
- // get the async (semaphore) locker specific to this key
- var keyLock = AsyncLock.Acquire(key);
+ if (_scopeAccessor.Value.HasScope(key))
+ {
+ throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
+ }
- using (await keyLock.LockAsync())
+ // Get the async (semaphore) locker specific to this key
+ using (await KeyedLock.LockAsync("cache:" + key, TimeSpan.FromMinutes(1)))
{
- if (!TryGet(key, out value))
+ if (!TryGet(key, independent, out value))
{
- value = await acquirer().ConfigureAwait(false);
- Set(key, value, duration);
- return value;
+ using (_scopeAccessor.Value.BeginScope(key))
+ {
+ value = await acquirer();
+ Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies);
+ return value;
+ }
}
}
return value;
}
- private async Task Test()
- {
- var t = await GetAsync("yo", async () =>
- {
- return await Task.FromResult(true);
- });
- }
-
- public void Set(string key, object value, TimeSpan? duration = null)
+ public void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null)
{
- _cache.Set(key, value ?? FakeNull, GetCacheItemPolicy(duration));
+ _cache.Set(key, value ?? FakeNull, GetCacheItemPolicy(duration, dependencies));
}
public bool Contains(string key)
@@ -127,7 +148,7 @@ public void Remove(string key)
_cache.Remove(key);
}
- public string[] Keys(string pattern)
+ public IEnumerable Keys(string pattern)
{
Guard.NotEmpty(pattern, nameof(pattern));
@@ -138,29 +159,54 @@ public string[] Keys(string pattern)
return keys.ToArray();
}
- return keys.Where(x => x.StartsWith(pattern, StringComparison.OrdinalIgnoreCase)).ToArray();
+ var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase);
+ return keys.Where(x => wildcard.IsMatch(x)).ToArray();
}
- public void RemoveByPattern(string pattern)
+ public int RemoveByPattern(string pattern)
{
- var keysToRemove = Keys(pattern);
-
lock (_cache)
{
+ var keysToRemove = Keys(pattern);
+ int count = 0;
+
// lock atomic operation
foreach (string key in keysToRemove)
{
_cache.Remove(key);
+ count++;
}
- }
- }
+
+ return count;
+ }
+ }
public void Clear()
{
- RemoveByPattern("*");
- }
+ // Faster way of clearing cache: https://stackoverflow.com/questions/8043381/how-do-i-clear-a-system-runtime-caching-memorycache
+ var oldCache = Interlocked.Exchange(ref _cache, CreateCache());
+ oldCache.Dispose();
+ GC.Collect();
+ }
- private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration)
+ public virtual ISet GetHashSet(string key, Func> acquirer = null)
+ {
+ var result = Get(key, () =>
+ {
+ var set = new MemorySet(this);
+ var items = acquirer?.Invoke();
+ if (items != null)
+ {
+ set.AddRange(items);
+ }
+
+ return set;
+ });
+
+ return result;
+ }
+
+ private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration, IEnumerable dependencies)
{
var absoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration;
@@ -168,16 +214,35 @@ private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration)
{
absoluteExpiration = DateTime.UtcNow + duration.Value;
}
-
+
var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = absoluteExpiration,
SlidingExpiration = ObjectCache.NoSlidingExpiration
};
+
+ if (dependencies != null && dependencies.Any())
+ {
+ // INFO: we can only depend on existing items, otherwise this entry will be removed immediately.
+ dependencies = dependencies.Where(x => x != null && _cache.Contains(x));
+ if (dependencies.Any())
+ {
+ cacheItemPolicy.ChangeMonitors.Add(_cache.CreateCacheEntryChangeMonitor(dependencies));
+ }
+ }
+
+ //cacheItemPolicy.RemovedCallback = OnRemoveEntry;
return cacheItemPolicy;
}
+ //private void OnRemoveEntry(CacheEntryRemovedArguments args)
+ //{
+ // if (args.RemovedReason == CacheEntryRemovedReason.ChangeMonitorChanged)
+ // {
+ // Debug.WriteLine("MEMCACHE: remove depending entry '{0}'.".FormatInvariant(args.CacheItem.Key));
+ // }
+ //}
protected override void OnDispose(bool disposing)
{
diff --git a/src/Libraries/SmartStore.Core/Caching/MemorySet.cs b/src/Libraries/SmartStore.Core/Caching/MemorySet.cs
new file mode 100644
index 0000000000..c88cacd703
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/MemorySet.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SmartStore.Core.Caching
+{
+ [Serializable]
+ internal class MemorySet : HashSet, ISet
+ {
+ private readonly ICacheManager _cache;
+
+ public MemorySet(ICacheManager cache)
+ {
+ _cache = cache;
+ }
+
+ public void AddRange(IEnumerable items)
+ {
+ base.UnionWith(items);
+ }
+
+ public bool Move(string destinationKey, string item)
+ {
+ if (this.Contains(item))
+ {
+ var target = _cache.GetHashSet(destinationKey);
+ return target.Add(item);
+ }
+
+ return false;
+ }
+
+ public long ExceptWith(params string[] keys)
+ {
+ return Combine(x => this.Except(x), keys);
+ }
+
+ public long IntersectWith(params string[] keys)
+ {
+ return Combine(x => this.Intersect(x), keys);
+ }
+
+ public long UnionWith(params string[] keys)
+ {
+ return Combine(x => this.Union(x), keys);
+ }
+
+ private long Combine(Func, IEnumerable> func, params string[] keys)
+ {
+ if (keys.Length == 0)
+ return 0;
+
+ var other = keys.SelectMany(x => _cache.GetHashSet(x)).Distinct();
+ var result = func(other);
+
+ this.Clear();
+ this.AddRange(other);
+
+ return this.Count;
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/NullCache.cs b/src/Libraries/SmartStore.Core/Caching/NullCache.cs
index d49d189f04..e75abfd91d 100644
--- a/src/Libraries/SmartStore.Core/Caching/NullCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/NullCache.cs
@@ -23,12 +23,12 @@ public bool IsDistributedCache
get { return false; }
}
- public T Get(string key)
+ public T Get(string key, bool independent = false)
{
return default(T);
}
- public T Get(string key, Func acquirer, TimeSpan? duration = null)
+ public T Get(string key, Func acquirer, TimeSpan? duration = null, bool independent = false)
{
if (acquirer == null)
{
@@ -37,7 +37,7 @@ public T Get(string key, Func acquirer, TimeSpan? duration = null)
return acquirer();
}
- public Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null)
+ public Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false)
{
if (acquirer == null)
{
@@ -46,7 +46,13 @@ public Task GetAsync(string key, Func> acquirer, TimeSpan? duratio
return acquirer();
}
- public void Set(string key, object value, TimeSpan? duration = null)
+
+ public ISet GetHashSet(string key, Func> acquirer = null)
+ {
+ return new MemorySet(this);
+ }
+
+ public void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null)
{
}
@@ -59,13 +65,14 @@ public void Remove(string key)
{
}
- public string[] Keys(string pattern)
+ public IEnumerable Keys(string pattern)
{
return new string[0];
}
- public void RemoveByPattern(string pattern)
+ public int RemoveByPattern(string pattern)
{
+ return 0;
}
public void Clear()
diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs
new file mode 100644
index 0000000000..9629fee3c5
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Autofac;
+using SmartStore.Core.Data;
+using SmartStore.Core.Domain.Blogs;
+using SmartStore.Core.Domain.Catalog;
+using SmartStore.Core.Domain.Discounts;
+using SmartStore.Core.Domain.Localization;
+using SmartStore.Core.Domain.News;
+using SmartStore.Core.Domain.Topics;
+using SmartStore.Utilities;
+
+namespace SmartStore.Core.Caching
+{
+ public delegate IEnumerable DisplayControlHandler(BaseEntity entity, IComponentContext ctx);
+
+ public partial class DisplayControl : IDisplayControl
+ {
+ private static readonly ConcurrentDictionary _handlers = new ConcurrentDictionary
+ {
+ [typeof(BlogComment)] = (x, c) => new[] { "b" + ((BlogComment)x).BlogPostId },
+ [typeof(BlogPost)] = (x, c) => new[] { "b" + x.Id },
+ [typeof(Category)] = (x, c) => new[] { "c" + x.Id },
+ [typeof(Manufacturer)] = (x, c) => new[] { "m" + x.Id },
+ [typeof(ProductBundleItem)] = (x, c) => new[] { "p" + ((ProductBundleItem)x).ProductId },
+ [typeof(ProductPicture)] = (x, c) => new[] { "p" + ((ProductPicture)x).ProductId },
+ [typeof(ProductSpecificationAttribute)] = (x, c) => new[] { "p" + ((ProductSpecificationAttribute)x).ProductId },
+ [typeof(ProductVariantAttributeCombination)]= (x, c) => new[] { "p" + ((ProductVariantAttributeCombination)x).ProductId },
+ [typeof(TierPrice)] = (x, c) => new[] { "p" + ((TierPrice)x).ProductId },
+ [typeof(CrossSellProduct)] = (x, c) => new[] { "p" + ((CrossSellProduct)x).ProductId1, "p" + ((CrossSellProduct)x).ProductId2 },
+ [typeof(RelatedProduct)] = (x, c) => new[] { "p" + ((RelatedProduct)x).ProductId1, "p" + ((RelatedProduct)x).ProductId2 },
+ [typeof(ProductCategory)] = (x, c) => new[] { "p" + ((ProductCategory)x).CategoryId, "p" + ((ProductCategory)x).ProductId },
+ [typeof(ProductManufacturer)] = (x, c) => new[] { "p" + ((ProductManufacturer)x).ManufacturerId, "p" + ((ProductManufacturer)x).ProductId },
+ [typeof(NewsItem)] = (x, c) => new[] { "n" + x.Id },
+ [typeof(NewsComment)] = (x, c) => new[] { "n" + ((NewsComment)x).NewsItemId },
+ [typeof(Topic)] = (x, c) => new[] { "t" + x.Id },
+ [typeof(SpecificationAttributeOption)] = (x, c) => ((SpecificationAttributeOption)x).ProductSpecificationAttributes.Select(y => "p" + y.ProductId),
+ [typeof(ProductTag)] = (x, c) => ((ProductTag)x).Products.Select(y => "p" + y.Id),
+ [typeof(Product)] = HandleProduct,
+ [typeof(SpecificationAttribute)] = HandleSpecificationAttribute,
+ [typeof(ProductVariantAttributeValue)] = HandleProductVariantAttributeValue,
+ [typeof(Discount)] = HandleDiscount,
+ [typeof(LocalizedProperty)] = HandleLocalizedProperty
+ };
+
+ private readonly HashSet _entities = new HashSet();
+ private readonly IComponentContext _componentContext;
+
+ private bool _isIdle;
+ private bool? _isUncacheableRequest;
+
+ public DisplayControl(IComponentContext componentContext)
+ {
+ _componentContext = componentContext;
+ }
+
+ #region Static
+
+ public static bool ContainsHandlerFor(Type type)
+ {
+ Guard.NotNull(type, nameof(type));
+
+ return _handlers.ContainsKey(type);
+ }
+
+ public static void RegisterHandlerFor(Type type, DisplayControlHandler handler)
+ {
+ Guard.NotNull(type, nameof(type));
+ Guard.NotNull(handler, nameof(handler));
+
+ _handlers.TryAdd(type, handler);
+ }
+
+ #endregion
+
+ #region Handlers
+
+ private static IEnumerable HandleProduct(BaseEntity entity, IComponentContext ctx)
+ {
+ var product = ((Product)entity);
+ yield return "p" + entity.Id;
+ if (product.ProductType == ProductType.GroupedProduct && product.ParentGroupedProductId > 0)
+ {
+ yield return "p" + product.ParentGroupedProductId;
+ }
+ }
+
+ private static IEnumerable HandleSpecificationAttribute(BaseEntity entity, IComponentContext ctx)
+ {
+ // Determine all affected products (which are assigned to this attribute).
+ var specAttrId = ((SpecificationAttribute)entity).Id;
+ var affectedProductIds = ctx.Resolve().Set().AsNoTracking()
+ .Where(x => x.SpecificationAttributeOption.SpecificationAttribute.Id == specAttrId)
+ .Select(x => x.ProductId)
+ .Distinct()
+ .ToList();
+
+ foreach (var id in affectedProductIds)
+ {
+ yield return "p" + id;
+ }
+ }
+
+ private static IEnumerable HandleProductVariantAttributeValue(BaseEntity entity, IComponentContext ctx)
+ {
+ var pva = ((ProductVariantAttributeValue)entity).ProductVariantAttribute;
+ if (pva != null)
+ {
+ yield return "p" + pva.ProductId;
+ }
+ }
+
+ private static IEnumerable HandleDiscount(BaseEntity entity, IComponentContext ctx)
+ {
+ var discount = (Discount)entity;
+ if (discount.DiscountType == DiscountType.AssignedToCategories)
+ {
+ foreach (var category in discount.AppliedToCategories)
+ {
+ yield return "c" + category.Id;
+ }
+ }
+ else if (discount.DiscountType == DiscountType.AssignedToSkus)
+ {
+ foreach (var product in discount.AppliedToProducts)
+ {
+ yield return "p" + product.Id;
+ }
+ }
+ }
+
+ private static IEnumerable HandleLocalizedProperty(BaseEntity entity, IComponentContext ctx)
+ {
+ var lp = (LocalizedProperty)entity;
+ string prefix = null;
+ BaseEntity targetEntity = null;
+
+ var dbContext = ctx.Resolve();
+
+ switch (lp.LocaleKeyGroup)
+ {
+ case nameof(Product):
+ prefix = "p";
+ break;
+ case nameof(Category):
+ prefix = "c";
+ break;
+ case nameof(Manufacturer):
+ prefix = "m";
+ break;
+ case nameof(Topic):
+ prefix = "t";
+ break;
+ case nameof(SpecificationAttribute):
+ targetEntity = dbContext.Set().Find(lp.EntityId);
+ break;
+ case nameof(SpecificationAttributeOption):
+ targetEntity = dbContext.Set().Find(lp.EntityId);
+ break;
+ case nameof(ProductVariantAttributeValue):
+ targetEntity = dbContext.Set().Find(lp.EntityId);
+ break;
+ }
+
+ if (prefix.HasValue())
+ {
+ yield return prefix + lp.EntityId;
+ }
+ else if (targetEntity != null)
+ {
+ var tags = ctx.Resolve().GetCacheControlTagsFor(targetEntity);
+ foreach (var tag in tags)
+ {
+ yield return tag;
+ }
+ }
+ }
+
+ #endregion
+
+ public IDisposable BeginIdleScope()
+ {
+ _isIdle = true;
+ return new ActionDisposable(() => _isIdle = false);
+ }
+
+ public virtual void Announce(BaseEntity entity)
+ {
+ if (!_isIdle && entity != null)
+ {
+ _entities.Add(entity);
+ }
+ }
+
+ public bool IsDisplayed(BaseEntity entity)
+ {
+ if (entity == null)
+ return false;
+
+ return _entities.Contains(entity);
+ }
+
+ public void MarkRequestAsUncacheable()
+ {
+ // First wins: subsequent calls should not be able to cancel this
+ if (!_isIdle)
+ _isUncacheableRequest = true;
+ }
+
+ public bool IsUncacheableRequest
+ {
+ get
+ {
+ return _isUncacheableRequest.GetValueOrDefault() == true;
+ }
+ }
+
+ public virtual IEnumerable GetCacheControlTagsFor(BaseEntity entity)
+ {
+ var empty = Enumerable.Empty();
+
+ if (entity == null || entity.IsTransientRecord())
+ {
+ return empty;
+ }
+
+ var type = entity.GetUnproxiedType();
+
+ if (!_handlers.TryGetValue(type, out var handler))
+ {
+ return empty;
+ }
+
+ return handler.Invoke(entity, _componentContext);
+ }
+
+ public IEnumerable GetAllCacheControlTags()
+ {
+ var tags = _entities
+ .Where(x => x.Id > 0)
+ .SelectMany(x => GetCacheControlTagsFor(x))
+ .Where(x => x != null)
+ .Distinct()
+ .ToArray();
+
+ return tags;
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/ICacheableRouteRegistrar.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/ICacheableRouteRegistrar.cs
new file mode 100644
index 0000000000..b30feeb1e4
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/ICacheableRouteRegistrar.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace SmartStore.Core.Caching
+{
+ public interface ICacheableRouteRegistrar
+ {
+ void RegisterCacheableRoute(params string[] routes);
+ void RemoveCacheableRoute(params string[] routes);
+ }
+
+ public class NullCacheableRouteRegistrar : ICacheableRouteRegistrar
+ {
+ public void RegisterCacheableRoute(params string[] routes)
+ {
+ }
+
+ public void RemoveCacheableRoute(params string[] routes)
+ {
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/IDisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IDisplayControl.cs
similarity index 95%
rename from src/Libraries/SmartStore.Core/Caching/IDisplayControl.cs
rename to src/Libraries/SmartStore.Core/Caching/OutputCache/IDisplayControl.cs
index 569b69c3f8..9646cf649e 100644
--- a/src/Libraries/SmartStore.Core/Caching/IDisplayControl.cs
+++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IDisplayControl.cs
@@ -19,6 +19,8 @@ public interface IDisplayControl
IEnumerable GetCacheControlTagsFor(BaseEntity entity);
IEnumerable GetAllCacheControlTags();
+
+ IDisposable BeginIdleScope();
}
public static class IDisplayControlExtensions
diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs
new file mode 100644
index 0000000000..234b67ced2
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using SmartStore.Core.Configuration;
+using SmartStore.Core.Data.Hooks;
+using SmartStore.Core.Infrastructure.DependencyManagement;
+using SmartStore.Utilities;
+
+namespace SmartStore.Core.Caching
+{
+ public class ObserveEntityContext
+ {
+ public IOutputCacheProvider OutputCacheProvider { get; set; }
+ public IDisplayControl DisplayControl { get; set; }
+ public BaseEntity Entity { get; set; }
+ public IHookedEntity EntityEntry { get; set; }
+ public bool Handled { get; set; }
+ public ContainerManager ServiceContainer { get; set; }
+ }
+
+ ///
+ /// Allows registration of output cache invalidation handlers
+ ///
+ public interface IOutputCacheInvalidationObserver
+ {
+ ///
+ /// Registers an entity observer. The passed observer is responsible for invalidating the output cache
+ /// by calling one of the invalidation methods in the instance.
+ /// The observer must then set the property to true
+ /// to signal the framework that it should skip executing subsequent observers.
+ ///
+ /// The observer action
+ ///
+ /// The implementation of this interface is singleton scoped.
+ /// Don't use objects with shorter lifetime in your handler as this will lead to memory leaks.
+ /// If your handler needs to call service methods, resolve required services
+ /// with .
+ ///
+ void ObserveEntity(Action observer);
+
+ IEnumerable> GetEntityObservers();
+
+ ///
+ /// Registers a setting key to be observed by the framework. If the value for the passed
+ /// setting key changes, the framework calls the handler.
+ /// The key can either be fully qualified - e.g. "CatalogSettings.ShowProductSku" -,
+ /// or prefixed - e.g. "CatalogSettings.*". The latter calls the invalidator when ANY CatalogSetting changes.
+ ///
+ ///
+ /// The invalidation action handler. If null is passed, the framework
+ /// uses the default invalidator, which is .
+ ///
+ void ObserveSetting(string settingKey, Action invalidationAction);
+
+ Action GetInvalidationActionForSetting(string settingKey);
+ }
+
+ public class NullOutputCacheInvalidationObserver : IOutputCacheInvalidationObserver
+ {
+ private static readonly IOutputCacheInvalidationObserver _instance = new NullOutputCacheInvalidationObserver();
+
+ public static IOutputCacheInvalidationObserver Instance
+ {
+ get { return _instance; }
+ }
+
+ public void ObserveEntity(Action observer)
+ {
+ }
+
+ public IEnumerable> GetEntityObservers()
+ {
+ return Enumerable.Empty>();
+ }
+
+ public void ObserveSetting(string settingKey, Action invalidationAction)
+ {
+ }
+
+ public Action GetInvalidationActionForSetting(string settingKey)
+ {
+ return null;
+ }
+ }
+
+ public static class IOutputCacheInvalidationObserverExtensions
+ {
+ public static void ObserveSetting(this IOutputCacheInvalidationObserver observer, string settingKey)
+ {
+ observer.ObserveSetting(settingKey, null);
+ }
+
+ ///
+ /// Registers a concrete setting class to be observed by the framework. If any setting property
+ /// of changes, the framework will purge the cache.
+ ///
+ /// The type of the concrete setting class to observe
+ ///
+ /// A property observer precedes a class observer.
+ ///
+ public static void ObserveSettings(this IOutputCacheInvalidationObserver observer)
+ where TSetting : ISettings
+ {
+ ObserveSettings(observer, null);
+ }
+
+ ///
+ /// Registers a concrete setting class to be observed by the framework. If any setting property
+ /// of changes, the framework will call the handler.
+ ///
+ /// The type of the concrete setting class to observe
+ ///
+ /// The invalidation action handler. If null is passed, the framework
+ /// uses the default invalidator, which is .
+ ///
+ ///
+ /// A property observer precedes a class observer.
+ ///
+ public static void ObserveSettings(
+ this IOutputCacheInvalidationObserver observer,
+ Action invalidationAction) where TSetting : ISettings
+ {
+ var key = typeof(TSetting).Name + ".*";
+ observer.ObserveSetting(key, invalidationAction);
+ }
+
+ ///
+ /// Registers a setting property to be observed by the framework. If the value for the passed
+ /// property changes, the framework will purge the cache.
+ ///
+ /// The type of the concrete setting class which contains the property
+ /// The property lambda
+ public static void ObserveSettingProperty(
+ this IOutputCacheInvalidationObserver observer,
+ Expression> propertyAccessor) where TSetting : ISettings
+ {
+ ObserveSettingProperty(observer, propertyAccessor, null);
+ }
+
+ ///
+ /// Registers a setting property to be observed by the framework. If the value for the passed
+ /// property changes, the framework will call the handler.
+ ///
+ /// The type of the concrete setting class which contains the property
+ /// The property lambda
+ ///
+ /// The invalidation action handler. If null is passed, the framework
+ /// uses the default invalidator, which is .
+ ///
+ public static void ObserveSettingProperty(
+ this IOutputCacheInvalidationObserver observer,
+ Expression> propertyAccessor,
+ Action invalidationAction) where TSetting : ISettings
+ {
+ Guard.NotNull(propertyAccessor, nameof(propertyAccessor));
+
+ var key = TypeHelper.NameOf(propertyAccessor, true);
+ observer.ObserveSetting(key, invalidationAction);
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Caching/IOutputCacheProvider.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheProvider.cs
similarity index 92%
rename from src/Libraries/SmartStore.Core/Caching/IOutputCacheProvider.cs
rename to src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheProvider.cs
index 90c6672f84..3e2aead128 100644
--- a/src/Libraries/SmartStore.Core/Caching/IOutputCacheProvider.cs
+++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheProvider.cs
@@ -17,6 +17,7 @@ public interface IOutputCacheProvider : IProvider
int Count();
int InvalidateByRoute(params string[] routes);
+ int InvalidateByPrefix(string keyPrefix);
int InvalidateByTag(params string[] tags);
}
}
diff --git a/src/Libraries/SmartStore.Core/Caching/NullOutputCacheProvider.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/NullOutputCacheProvider.cs
similarity index 78%
rename from src/Libraries/SmartStore.Core/Caching/NullOutputCacheProvider.cs
rename to src/Libraries/SmartStore.Core/Caching/OutputCache/NullOutputCacheProvider.cs
index af93195853..f9d2ba828a 100644
--- a/src/Libraries/SmartStore.Core/Caching/NullOutputCacheProvider.cs
+++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/NullOutputCacheProvider.cs
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using SmartStore.Core.Plugins;
namespace SmartStore.Core.Caching
@@ -11,6 +8,13 @@ namespace SmartStore.Core.Caching
[FriendlyName("Idle")]
public class NullOutputCacheProvider : IOutputCacheProvider
{
+ private static readonly IOutputCacheProvider s_instance = new NullOutputCacheProvider();
+
+ public static IOutputCacheProvider Instance
+ {
+ get { return s_instance; }
+ }
+
public IPagedList All(int pageIndex, int pageSize, bool withContent = false)
{
return new PagedList(new List(), pageIndex, pageSize);
@@ -36,6 +40,11 @@ public int InvalidateByRoute(params string[] routes)
return 0;
}
+ public int InvalidateByPrefix(string keyPrefix)
+ {
+ return 0;
+ }
+
public int InvalidateByTag(params string[] tags)
{
return 0;
diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCacheItem.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/OutputCacheItem.cs
similarity index 100%
rename from src/Libraries/SmartStore.Core/Caching/OutputCacheItem.cs
rename to src/Libraries/SmartStore.Core/Caching/OutputCache/OutputCacheItem.cs
diff --git a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs
index d38c53061e..5fb74d2400 100644
--- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs
@@ -4,10 +4,11 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
+using SmartStore.Utilities;
namespace SmartStore.Core.Caching
{
- public class RequestCache : IRequestCache
+ public class RequestCache : DisposableObject, IRequestCache
{
const string RegionName = "SmartStoreNET:";
@@ -46,7 +47,7 @@ public T Get(string key, Func acquirer)
return default(T);
}
- public void Set(string key, object value)
+ public void Put(string key, object value)
{
var items = GetItems();
@@ -81,7 +82,7 @@ public void RemoveByPattern(string pattern)
foreach (string key in keysToRemove)
{
- items.Remove(key);
+ items.Remove(BuildKey(key));
}
}
@@ -100,19 +101,20 @@ public IEnumerable Keys(string pattern)
var prefixLen = RegionName.Length;
pattern = pattern.NullEmpty() ?? "*";
+ var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase);
var enumerator = items.GetEnumerator();
while (enumerator.MoveNext())
{
- string key = enumerator.Key as string;
- if (key == null)
- continue;
- if (key.StartsWith(RegionName))
+ if (enumerator.Key is string key)
{
- key = key.Substring(prefixLen);
- if (pattern == "*" || key.StartsWith(pattern, StringComparison.OrdinalIgnoreCase))
+ if (key.StartsWith(RegionName))
{
- yield return key;
+ key = key.Substring(prefixLen);
+ if (pattern == "*" || wildcard.IsMatch(key))
+ {
+ yield return key;
+ }
}
}
}
@@ -122,5 +124,11 @@ private string BuildKey(string key)
{
return RegionName + key.EmptyNull();
}
+
+ protected override void OnDispose(bool disposing)
+ {
+ if (disposing)
+ Clear();
+ }
}
}
diff --git a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs
index d9e6c700b6..86d5b00249 100644
--- a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs
+++ b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs
@@ -3,8 +3,9 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
-using System.Linq.Expressions;
using Newtonsoft.Json;
+using SmartStore.Utilities;
+using System.Threading;
namespace SmartStore.Collections
{
@@ -15,40 +16,65 @@ namespace SmartStore.Collections
/// The type of value.
[JsonConverter(typeof(ConcurrentMultiMapConverter))]
[Serializable]
- public class ConcurrentMultimap : IEnumerable>>
+ public class ConcurrentMultimap : IEnumerable>>
{
- private readonly ConcurrentDictionary> _items;
- private static readonly Func