diff --git a/.gitignore b/.gitignore
index ce0460f432..a45279fb21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,16 +25,6 @@ local.properties
# PDT-specific
.buildpath
-###########
-## Symlinks
-###########
-
-*-sym.*
-*-symdir/
-[Ee]xternal[Ff]iles/
-/src/Plugins/*-sym
-/src/Presentation/SmartStore.Web/Themes/*-sym
-
###########################################################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@@ -43,23 +33,48 @@ local.properties
# User-specific files
*.suo
*.user
+*.userosscache
*.sln.docstates
-# Build results
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+# Build results
[Dd]ebug/
+[Dd]ebugPublic/
[Rr]elease/
+[Rr]eleases/
x64/
-# build/
+x86/
+bld/
[Bb]in/
[Oo]bj/
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
*_i.c
*_p.c
+*_i.h
*.ilk
*.meta
*.obj
@@ -79,6 +94,7 @@ x64/
*.vssscc
.builds
*.pidb
+*.svclog
*.scc
# Visual C++ cache files
@@ -93,6 +109,7 @@ ipch/
*.psess
*.vsp
*.vspx
+*.sap
# Guidance Automation Toolkit
*.gpState
@@ -108,8 +125,16 @@ _TeamCity*
*.dotCover
# NCrunch
-*.ncrunch*
+_NCrunch_*
.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
# Installshield output folder
[Ee]xpress/
@@ -128,31 +153,58 @@ DocProject/Help/html
publish/
# Publish Web Output
-*.Publish.xml
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
-
-# NuGet Packages Directory
-## TODO: If you have NuGet Package Restore enabled, uncomment the next line
-/src/packages
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
# Windows Azure Build Output
csx
*.build.csdef
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
# Windows Store app package directory
AppPackages/
+BundleArtifacts/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
# Others
# sql/
-*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
-*.[Pp]ublish.xml
+*.dbproj.schemaview
*.pfx
*.publishsettings
+node_modules/
+orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
@@ -165,12 +217,42 @@ UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
-App_Data/*.mdf
-App_Data/*.ldf
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
-###################
-## Windows detritus
-###################
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+
+# FAKE - F# Make
+.fake/
# Windows image file caches
Thumbs.db
@@ -185,6 +267,21 @@ $RECYCLE.BIN/
# Mac crap
.DS_Store
+
+###########
+## Symlinks
+###########
+
+*-sym.*
+*-symdir/
+[Ee]xternal[Ff]iles/
+/src/Plugins/*-sym
+/src/Presentation/SmartStore.Web/Themes/*-sym
+
+
+###########
+## Specific
+###########
# Backups
[Bb]ackup/
[Bb]ackups/
@@ -194,21 +291,27 @@ _[Bb]ackups/
*.orig
Kopie von*
-###########
-## Specific
-###########
/build
/src/Presentation/SmartStore.Web/Plugins
/src/Presentation/SmartStore.Web/App_Data/_temp/
+/src/Presentation/SmartStore.Web/App_Data/ExportProfiles/
+/src/Presentation/SmartStore.Web/App_Data/ImportProfiles/
/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
# Import/Export
/src/Presentation/SmartStore.Web/Content/files/[Ee]xport[Ii]mport/*.*
+/src/Presentation/SmartStore.Web/[Ee]xchange/*
# Media
/src/Presentation/SmartStore.Web/Media/[Tt]humbs
/src/Presentation/SmartStore.Web/Media/[Uu]ploaded
+# Tools
+/src/Tools/SmartStore.WebApi.Client/packages
+/src/Tools/SmartStore.WebApi.Client/SmartStore.WebApi.Client.sln
+
src/SmartStoreNET.Packager.sln
+Log.txt
diff --git a/CREDITS.txt b/CREDITS.txt
index 7e43a00c90..88b2d0a6be 100644
--- a/CREDITS.txt
+++ b/CREDITS.txt
@@ -12,14 +12,14 @@ copyright notices, and licenses.
51Degrees
------
-WebSite: http://http://51degrees.mobi/
+---------------------------------------------
+WebSite: http://51degrees.mobi/
Copyright: Copyright 2010 - 2013 51Degrees.mobi Limited
License: Mozilla Public License 2.0 (MPL-2.0)
ANTLR
------
+---------------------------------------------
Website: http://www.antlr.org
Copyright: Copyright ANTLR / Terence Parr 2014
License: BSD License
@@ -27,7 +27,7 @@ License: BSD License
ASP.NET MVC
------
+---------------------------------------------
Website: http://aspnet.codeplex.com/wikipage?title=MVC&referringTitle=Home
Copyright: Copyright (c) 2008-2011 Microsoft Corporation
License: MICROSOFT ASP.NET MODEL VIEW CONTROLLER 3 EULA
@@ -35,7 +35,7 @@ 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
@@ -43,155 +43,143 @@ License: MIT
AutoMapper
------
-WebSite: http://http://www.automapper.org
+---------------------------------------------
+WebSite: http://www.automapper.org
Copyright: Copyright 2008-2013 Jimmy Bogard and other contributors
License: MIT
Bundle Transformer
------
+---------------------------------------------
Website: https://bundletransformer.codeplex.com
Copyright: Copyright Andrey Taritsyn 2014
License: Apache License 2.0 (Apache)
DotNetOpenAuth
------
-Website: http://http://dotnetopenauth.net/
+---------------------------------------------
+Website: http://dotnetopenauth.net/
Copyright: Copyright (c) 2011, Outercurve Foundation
License: MS-PL
DotNetZip
------
+---------------------------------------------
Website: http://dotnetzip.codeplex.com/
Copyright:
License: MS-PL
Entity Framework
------
+---------------------------------------------
Website: https://entityframework.codeplex.com
Copyright: Copyright (c) 2012-2014 Microsoft Corporation
License: Apache License 2.0 (Apache)
EPPlus
------
+---------------------------------------------
Website: https://epplus.codeplex.com/
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/
License: Apache License 2.0 (Apache)
Glimpse
------
+---------------------------------------------
Website: https://github.com/Glimpse/Glimpse
Copyright: Copyright (C) 2014 Glimpse contributors (http://getglimpse.com/Community)
License: Apache License 2.0 (Apache)
ImageResizer.NET
------
+---------------------------------------------
Website: https://imageresizing.net/
Copyright: Copyright (c) 2012 Imazen
License: Freedom License
-iTextSharp
------
-Website: http://itextpdf.com/
-License: Affero General Public License
-
-
JavaScriptEngineSwitcher
------
+---------------------------------------------
Website: https://github.com/Taritsyn/JavaScriptEngineSwitcher
Copyright: Copyright Andrey Taritsyn 2014
License: Apache License 2.0 (Apache)
jQuery
------
+---------------------------------------------
Website: http://jquery.com
Copyright: Copyright (c) 2010 John Resig, http://jquery.com
License: MIT
jQuery ScrollTo
------
+---------------------------------------------
Website: http://flesler.blogspot.com
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
------
+---------------------------------------------
Website: http://james.newtonking.com/
Copyright: Copyright (c) 2007 James Newton-King
License: MIT
-LESS CSS for .NET
------
-Website: http://www.dotlesscss.org/
-License: Apache License v2.0
-
-
GeoLite Free by MaxMind
------
+---------------------------------------------
Website: http://www.maxmind.com
Copyright: Copyright (c) 2014 Maxmind
License: Creative Commons Attribution-ShareAlike 3.0 Unported License
Microsoft SQL Server Compact 4.0
------
+---------------------------------------------
Website: http://www.microsoft.com
Copyright: Copyright (c) 2011 Microsoft Corporation
License: Custom EULA - http://www.microsoft.com/web/webpi/eula/SQLCE_EULA_ENU.rtf
MiniProfiler
------
+---------------------------------------------
Website: http://miniprofiler.com/
License: Apache License, Version 2.0
Modernizr
------
+---------------------------------------------
Website: http://modernizr.com/
License: MIT
NuGet
------
+---------------------------------------------
Website: http://nuget.codeplex.com
Copyright: Copyright 2010-2011 Outercurve Foundation
License: Apache Software Foundation License 2.0
nUnit
------
+---------------------------------------------
Website: http://www.nunit.org/index.php
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
@@ -199,26 +187,32 @@ License: Derived from zlib: http://nunit.org/index.php?p=license&r=2.4
Telerik MVC Extensions
------
+---------------------------------------------
Website: http://www.telerik.com/
License: GPL v3.0
TinyMCE
------
+---------------------------------------------
WebSite: http://tinymce.moxiecode.com/
Copyright: Copyright (c) 2003-2009 Moxiecode Systems AB
License: LGPL 2.1
Twitter Bootstrap
------
+---------------------------------------------
Website: http://twitter.github.io/bootstrap/
License: Apache License v2.0
underscore.js
------
+---------------------------------------------
Website: http://underscorejs.org
Copyright: Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
License: MIT
+
+
+wkhtmltopdf
+---------------------------------------------
+Website: http://wkhtmltopdf.org/
+License: LGPL
diff --git a/README.md b/README.md
index adabb68abd..a7a83aee3a 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,32 @@
-# [SmartStore.NET](http://www.smartstore.com/net/en)
+## Overview
-
+
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.
-**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, datasheets, ESD, discounts, coupons and many more.
+**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.
-A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping handling among others makes SmartStore.NET a powerful all-in-one solution fulfilling all your needs... totally FREE!
+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 delivers a beautiful and configurable shop frontend 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 (one-page) checkout process... they all provide a perfect shopping experience to your buyers.
+**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.
-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/net](http://www.smartstore.com/net)
-* **Community:** [http://community.smartstore.com](http://community.smartstore.com)
+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)
## Highlights
### Technology & Design
-* State of the art architecture thanks to ASP.NET 4.5, ASP.NET MVC 5, Entity Framework 6 and Domain Driven Design
+* 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
-* Consistent and sophisticated use of modern components such as jQuery, Twitter Bootstrap, LESS & more in the frontend and backend.
+* (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
@@ -34,8 +36,8 @@ The state-of-the-art architecture of SmartStore.NET - with ASP.NET 4.5 + MVC 5,
* Multi-Store support
* Unlimited number of products and categories
-* (NEW) Product Bundles
-* (NEW) RESTful WebApi
+* Product Bundles
+* RESTful WebApi
* Multi-language support
* 100% Mobile optimized
* Perfectly optimized for search engines
@@ -52,16 +54,19 @@ The state-of-the-art architecture of SmartStore.NET - with ASP.NET 4.5 + MVC 5,
* and many more...
## Project Status
-SmartStore.NET V2 has been released on April 04. 2014. The highlights are:
-
-* RESTful **WebApi**
-* Highly optimized and **simplified product management**
-* Product **Bundles**
-* Considerably **faster app startup** and page processing
-* New variant attribute type: **Linked Product**
-* **Package upload** for plugins and themes
-* Lightning **fast asynchronous Excel import** with progress indicators and detailed reports
-* For developers: Code-based Entity Framework **database migrations**
+SmartStore.NET V2.5.0 has been released on March 03, 2016. The highlights are:
+
+ * New import/export framework (profiles, filters, mapping, projections, scheduling, deployment... just everything!)
+ * TaskScheduler: Rewritten from scratch to be suitable for Web Farms (including support for cron expressions)
+ * Payment and shipping methods by customer roles
+ * Restrict payment methods to countries
+ * Restrict payment methods to shipping methods
+ * Email attachment support for message templates
+ * Attach order invoice PDF automatically to order notification emails
+ * Overall performance increase
+ * Lots of bug fixes
+
+
##Try it online
diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets
index 317cce21ad..f184eaa7eb 100644
--- a/SmartStoreNET.Tasks.Targets
+++ b/SmartStoreNET.Tasks.Targets
@@ -2,7 +2,7 @@
-
+
@@ -44,7 +44,7 @@
x86
$(BUILD_NUMBER)
- 2.0.2
+ 2.6.0
$(StageFolder)
.$(Version)
@@ -114,7 +114,6 @@
-
@@ -125,6 +124,9 @@
+
+
+
diff --git a/assets/SmartStoreNET.png b/assets/SmartStoreNET.png
index b1986d38f8..1fc5ca5409 100644
Binary files a/assets/SmartStoreNET.png and b/assets/SmartStoreNET.png differ
diff --git a/build.bat b/build.bat
index e017b455af..b58175809a 100644
--- a/build.bat
+++ b/build.bat
@@ -1,21 +1,24 @@
-set MSBuildPath=%windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
+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"
-@IF NOT EXIST %MSBuildPath% @ECHO COULDN'T FIND MSBUILD: %MSBuildPath% (Is .NET 4 installed?)
-ELSE GOTO END
+ "%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 build
+ )
+)
+
+echo "Unable to detect suitable environment. Build may not succeed."
-:CheckOS
-IF EXIST "%PROGRAMFILES(X86)%" (GOTO 64BIT) ELSE (GOTO 32BIT)
+:build
-:64BIT
-echo 64-bit...
-set MSBuildPath="%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild.exe"
-GOTO END
-
-:32BIT
-echo 32-bit...
-set MSBuildPath="%ProgramFiles%\MSBuild\12.0\Bin\MSBuild.exe"
-GOTO END
-
-:END
-
-%MSBuildPath% SmartStoreNET.proj /p:DebugSymbols=false /p:DebugType=None /P:SlnName=SmartStoreNET /maxcpucount %*
\ No newline at end of file
+msbuild SmartStoreNET.proj /p:DebugSymbols=false /p:DebugType=None /P:SlnName=SmartStoreNET /maxcpucount %*
\ No newline at end of file
diff --git a/changelog.md b/changelog.md
index 009070cb03..0dac88a9fd 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,20 +1,407 @@
-#Release Notes#
-
-##SmartStore.NET 2.1#
-
-###New Features###
-* (Developer) Overhauled plugin architecture:
+# Release Notes
+
+## SmartStore.NET 2.6
+
+### Highlights
+* 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.
+
+### New Features
+* #961 Fix "Open Redirection Vulnerability"
+* #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party
+* #870 Implement address import for customers (both billing & shipping address)
+* #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers
+* Import localized SEO names for product and categories
+* #477 Implement option to specify the number of exported and imported pictures
+* #859 Make checkout attributes suitable for multi-stores
+* Product details: Select attribute and gift card values by query string parameters
+* #950 make contact form comply with current German law
+
+### Improvements
+* Major improvements in Importer: better field mapping, higher performance, bug fixes etc.
+* (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.
+* Localization & SEO: language switcher now takes language specific SEO slugs into account when building links
+* Smarter import of plugin resource files with graceful fallbacks (de-DE > de > de-* > en-US > en > en-* > *)
+* (Perf) Faster language resource file import
+* Exports the product detail link including the attribute query string when exporting attribute combinations
+* #918 Compare products: Display base price information
+* Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email
+* #913 Use HTML5 Input types (tel, email)
+* Added paging to frontend order list
+* Added paging to backend checkout attribute list
+* #977 Show PAngV base/delivery amount also
+* Updated LiveEngage LiveChat plugin
+
+### Bugfixes
+* TaskScheduler could fail polling when primary store url is an external IP address
+* Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage).
+* "The provider failed at the Execute method: Member 'CurrentValues' cannot be called for the entity of type 'Product'" when exporting product attribute combinations
+* Bundles without selected attributes could throw an exception on product detail page
+* GMC feed did not export the product type and Billiger did not export shop_cat (category path)
+* The error message of a payment provider when capturing a payment was not displayed
+* Adding new shipping method threw an exception
+* Attribute Values: Assigning IsPreselected to more than one value causes an error
+* BizImporter: fixed redirection bug when default file extension in .biz wasn't .html
+* Fixed: Export deployment emails were always send manually
+* Manually notifying gift card recipient threw an exception
+* Loading shipping by weight grid failed under SQL CE
+* #949 Import: ProcessSlugs does not process explicitly specified "SeName", if product name did not change in an update scenario
+* Customer import: Creates customer role duplicates for inserted customers
+* GMC feed does not generate the sale price if the sale price is set for a future date
+* Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child"
+* Download nor sample download were removable when editing product
+* Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'".
+* #921 Specification attribute options with single quotation marks are causing a Javascript error
+* #971 Product is added to cart automatically if it has a non-required file upload attribute
+* #973 Bundle item upload is nowhere linked
+* Base price in product list ignored PriceDisplayType (catalog settings) and possibly displayed the wrong base price info
+* Private messages: Fixes "No route in the route table matches the supplied values"
+* Payone: Hash string incorrect for frontend API payments where the order has more than 9 products
+* Export mail notification: Download link not working if SSL is enabled
+* Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times
+* #986 File uploads possible through /content/filemanager/index.html
+
+
+## SmartStore.NET 2.5
+
+### Highlights
+ * New import/export framework (profiles, filters, mapping, projections, scheduling, deployment... just everything!)
+ * TaskScheduler: Rewritten from scratch to be suitable for Web Farms (including support for cron expressions)
+ * Payment and shipping methods by customer roles
+ * Restrict payment methods to countries
+ * Restrict payment methods to shipping methods
+ * Email attachment support for message templates
+ * Attach order invoice PDF automatically to order notification emails
+ * Overall performance increase
+ * Lots of bug fixes
+
+### New Features
+* New export and import framework
+* Import of customer and category data
+* #141 Payment and shipping methods by customer roles
+* #67 Restrict payment methods to countries
+* #94 Restrict payment methods to shipping methods
+* #747 Restrict payment methods by old versus new customer (plugin)
+* #584 Email attachment support for message templates
+* Attach order invoice PDF automatically to order notification emails
+* #526 Min/Max amount option for which the payment method should be offered during checkout
+* (Dev) New _SyncMapping_ service: enables easier entity synchronization with external systems
+* (Dev) #792 ViewEngine: Enable vbhtml views per configuration
+* (Dev) Plugin developers can now render child actions into a dynamically created special tab called 'Plugins'
+* #718 ShopConnector: Import option for "Published" and "Disable buy\wishlist button"
+* #702 Facebook and Twitter external authentication suitable for multi-stores
+* New scheduled task: Clear e-mail queue
+* New scheduled task: Clear uploadeded transient media files
+* #704 Make primary store currency suitable for multi-stores
+* #727 Web-API: Option to deactivate TimestampOlderThanLastRequest validation
+* #731 Web-API: Allow deletion and inserting of product category and manufacturer assignments
+* #733 Option to set a display order for homepage products
+* #607 HTML capable full description for payment methods displayed in checkout
+* #732 Product list: Option to display the pre-selected price instead of the lowest price
+* New payment provider for Offline Payment Plugin: Purchase Order Number
+* #202 Implement option for product list 'default sort order'
+* #360 Import & export product variant combinations
+* #722 System > SEO Names: Implement editing of an UrlRecord
+* Admin > System > System Info shows used application memory (RAM)
+* Added option to make VATIN mandatory during customer registration
+* #840 Activity log: Have option to exclude search engine activity
+* #841 Activity log for deleting an order
+* More settings to control creation of SEO names
+* GMC feed: Supporting fields multipack, bundle, adult, energy efficiency class and custom label (0 to 4)
+* #760 Setting to set a customer role for new registered users
+* #800 Multi-store: Option to display all orders of all stores for customer in frontend
+* #457 Added option to hide the default image for categories and products
+* #451 Add message token for product shipping surcharge
+* #436 Make %Order.Product(s)% token to link the product detail page and a add product thumbnail
+* #339 Meta robots setting for page indexing of search engines
+* PayPal: Option for API security protocol
+* Product filter: Option to sort filter results by their display order rather than by number of matches
+* Elmar Shopinfo: Option to export delivery time as availability
+* #654 Place user agreement for downloadable files in checkout process
+* #398 EU law: add 'revocation' form and revocation waiver for ESD
+* #738 Implement download of pictures via URLs in product import
+* Web-API: Bridge to import framework: uploading import files to import profile directory
+* Setting to round down calculated reward points
+* #695 Implement checkbox in checkout to let customers subscribe to newsletters
+* #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) 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
+* TaskScheduler: Supports cron expressions to define task execution frequency
+* TaskScheduler: Editing tasks does not require app restart anymore
+* TaskScheduler: Enhanced UI
+* #721 Message Queue: implemented "Delete all"
+* #725 Prevent LowestProductPrice being 0
+* #709 News feed produced invalid RSS feed. Added content:encoded. Added maximum news age setting for feed export.
+* #735 Include SKUs of attribute combinations when filtering the backend product list
+* Filter for homepage and published\unpublished products in backend product list
+* Reduce database round trips initiated by price calculation
+* Google Analytics: added support for mobile devices
+* (Dev) TaskScheduler: Tasks can propagate progress info (percentage & message)
+* (Dev) TaskScheduler: Cancellation request is sent to tasks on app shutdown
+* ShippingByWeight & ShippingByTotal: Support for multiple zip ranges (comma separated)
+* Two more options to handle customer numbers: display customer number in frontend & let customers enter their customer number if it's still empty
+* #62 free shipping info on product detail page
+* Display base price in CompactProductBox
+* Automatically redirect to referrer after login
+* #826 Image gallery: the viewport height was fixed to 300 px, but now respects MediaSettings > ImageSize.
+* #249 Make UI editor for 'SeoSettings.ExtraRobotsDisallows'
+* Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice.
+* #778 Web-API: Increase MaxExpansionDepth for using expand pathes
+* #767 Remove assignments to a grouped product if the grouped product is deleted
+* #773 Reduce number of guest records created by search engine requests
+* #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links
+* Simplified handling of SEO names
+* URLs are not converted to lower case anymore
+* Product grid sortable by name, price and created on
+* #26 Display company or name in order list
+* Added inline editing of country grid
+* #790 Improved language editing
+* #843 Implement a product picker
+* #850 Use new product picker for selecting required products
+* Trusted Shops: badge will be displayed in mobile themes, payment info link replaced compare list link in footer
+* Product filter: Specification attributes are sorted by display order rather than alphabetically by name
+* #856 Don't route topics which are excluded from sitemap
+* #851 Replace reCAPTCHA with "I'm not a robot" CAPTCHA
+* #713 Display gift card remaining amount in frontend order details and order messages
+* #736 Render PayPal Express Button in minibasket
+* PayPal: Support for partial refunds
+* Offline credit card payment: Option to exclude credit card types
+
+### Bugfixes
+* #523 Redirecting to payment provider performed by core instead of plugin
+* Preselected price was wrong for product attributes with multiple preselected values (same on product detail page)
+* #749 Visual Studio 2015 compilation error: CS0009: Metadata file. SmartStore.Licensing.dll could not be opened -- Illegal tables in compressed metadata stream
+* PayPal Express: fixed capture method
+* #770 Resizing browser with product details page causes product image to disappear
+* GMC feed: Availability value "available for order" deprecated
+* Mobile: Shopping cart warnings weren't displayed to customers
+* Tax provider and payment method were automatically activated when there were no active provider\method
+* #784 Biz-Importer: Name of delivery time must not be imported empty
+* #776 Preview: Manufacturer and Product in Multi Store
+* #755 Some methods still loading all products in one go
+* #796 Selected specification in product filter mask is displayed with default language (not localized)
+* #805 Product filter is reset if 'product sorting' or 'view mode' or 'amount of displayed products per page' is changed
+* Hide link to a topic page if it is limited to stores
+* #829 Activity log: Searching by customer email out of function
+* Product import: Store mappings were not applied when inserting new records
+* Faulty permission handling in ajax grid actions (no message, infinite loading icon)
+* Grouped products: Display order was not correct
+* Deletion of a customer could delete all newsletter subscriptions
+* PayPal: Fixed "The request was aborted: Could not create SSL/TLS secure channel."
+* PayPal Express: Void and refund out of function ("The transaction id is not valid")
+* Customer could not delete his avatar
+* Facebook authentication: Email missing in verification
+* Attribute with a product linkage throws exception if added to cart
+* Number of products per product tag could be incorrect in a multi-store
+
+
+## SmartStore.NET 2.2.2
+
+### New Features
+* SmartStore.NET User Guide
+* #210 Implement multi-store support for import/export
+* Added zip code to shipping by weight computation method
+* Skrill payment plugin (distributed via Marketplace)
+* (Dev) DevTool plugin: added option to display all widget zones in public store
+* New options for manufacturer display on the homepage
+* Added optional customer number field
+
+### Improvements
+* (Perf) several minor optimizations for faster app startup and page rendering
+* UI: optimized image gallery widget (white background & nicer animations) + enhanced modal dialog fade animations
+* (Soft) deletion of SEO slug supporting entities now also deletes the corresponding url records
+* License checker now supports IDN mapping for domain names
+* #716 Supporting of paged google-product data query for SQL-Server Compact Edition
+* #648 Add hint for * at mandatory form fields at address creation
+* Added link to imprint and disclaimer to footer in mobile theme
+* #521 Display bonus points in order export
+* Updated GMC taxonomy files
+* MsieJsEngine now is the default LESS script engine
+
+### Bugfixes
+* #694 Product variant attribute in product page should not be preselected implicitly
+* Fixed: If currencies are limited to one for a multi-store, this currency should dominate the setting for the primary store currency
+* #563 Scheduled Tasks: ensure that 'LastEndUtc' is ALWAYS set
+* Topics grid: fixed 'maxJsonLength exceeded' error
+* Debitoor: Fixed "The property named 'lines.0.productOrService' should be defined"
+* Send currency code of primary store currency (not of working currency) to payment gateway
+* #691 Product quantity not added to cart on mobile theme
+* #186 Mobile: variant images do not refresh
+* #671 Bundle products: display base price according to applied discount
+* #619 Display base price according to applied tier price
+* #726 PAngV: basket displays wrong base price when attribute price adjustment has been set
+* Weight adjustment of attributes weren't applied in shopping cart overview
+* Shipping by weight calculates wrong surcharge if attribute combination prices are set
+* Don't let database hooks call other hooks.
+* There was no payment redirect if only one payment method is available in checkout
+
+
+## SmartStore.NET 2.2.1
+
+### New Features
+* #582 GMC feed: Option to include\exclude a product
+* #652 Web API: Support for file upload and multipart mime
+* New option to display product thumbnails in instant search (NOTE: the feature can be enabled via _Configuration > Settings > Catalog Settings > Search_)
+* #594 Option to prevent a message from being automatically sent
+* Category filtering by store for backend category tree and list
+* #554 Flag to indicate that a new payment notification (IPN) arrived for an order since last opening\editing of the order
+* Setting whether and how to display sub-categories on a category page (hide, above products, bottom of page)
+* Debitoor: Extend option "Book invoice if paid" to also (optionally) mail the invoice as a PDF
+* #508 Limit country settings to stores
+* #205 Add multistore support for forums
+
+### Improvements
+* PDF converter: first init is much faster now
+* Localized properties for forums and forum groups
+
+### Bugfixes
+* PDF converter: fixed _QFont::setPixelSize_ bug
+* Fixed System.NullReferenceException at admin/order/pdfpackagingslips
+* #660 Don't show shipping cost hint in product detail page if product has free shipping
+
+## SmartStore.NET 2.2.0
+
+### New Features
+* New mobile theme: _MobileLight_ (a light variant of the default mobile theme)
+* Localization: in a multi-language environment missing language resources fall back to default language's resources (instead of returning the ugly resource key)
+* Quantity unit management
+* #428 New option to override global category list view type
+* #672 New option to automatically create mutual associations for cross sell and related products
+* #485 Enable shop admin to change creation date of a blog entry
+* #258 Implement email validation in checkout
+* New option to specify the maximum number of displayed filter items
+* New option to specify whether all filter groups should be displayed expanded
+* #459 New field to specify html tag for page titles on widget level
+* Added _shrink database_ to backend UI
+* #588 Alternative localized category name used as title for category pages
+* #592 Add a second category description displayed beyond products on category page
+* Promotion feeds now export the preselected price (including price adjustments and overrides)
+* Implement 'free shipping threshold' for feed creation
+* _Billiger_ feed: Implement new field for shipping costs to austria
+* (Developer) Added `BeginTransaction()` and `UseTransaction()` methods to `IDbContext`
+
+### Improvements
+* (Perf) product list rendering up to 10x (!) faster now (depends on page size and view mode)
+* The data grid in the backend now preserves client state between requests (page, pageSize, sorting, filtering etc.)
+* Excel Import & Export: much lower memory consumption and increased performance
+* Moving pictures from DB to FS or vice versa is lightning fast now, consumes much lower memory and is encapsulated in a transaction which ensures reliable rollback after failure. Plus the database gets automatically shrinked after moving to FS.
+* Feed plugins: product query now paged to reduce memory payload
+* #589 Backend product list: add filter for products with "no category mapping" and "no manufacturer mapping"
+* Minor UI tweaks in checkout process
+* Payone: CC-Check via client API, not via Server API (requires PCI certification)
+* #189 Allow deletion of multiple reviews
+* #622 UI: Redesign table in Sales > Orders > Order > Tab Products
+* #625 Bundles can be ordered if an attribute combination of a bundle item is not available
+* #666 Export addresses in customer export
+* New shopping cart setting ShowItemsFromWishlistToCartButton
+* XML product export now is paged and more data gets exported
+* #560 Return requests: Add fields for last update, last update of requested action and general notes
+* #626 Add `IsActive` flag for checkout attributes
+
+### Bugfixes
+* Instant search box did not display all results when SQL Fulltext Search was enabled
+* Print order as PDF redirected to login although the admin already was logged in
+* #621 PDF Order: does not take overridden attribute combination price into account (in order line)
+* _Amazon payments_: Declined authorization IPN did not void the payment status
+* Fixed „Payment method couldn't be loaded“ when order amount is zero
+* #598 Wrong input parameter name for ReturnRequestSubmit
+* #557 Localize MVC validation strings
+* Fixed rare bug "The length of the string exceeds the value set on the maxJsonLength property" (Controller: Order, Action: OrderNotesSelect)
+* _Debitoor_: Adding order notes can result in infinite order update event loop with thousands of order notes
+* Tax rates persisted on order item level to avoid rounding issues (required for Debitoor, Accarda and Payone)
+* Hide additional shipping surcharge when display prices permission is not granted
+* Fixed "Adding a relationship with an entity which is in the Deleted state is not allowed" when adding bundles to cart
+* Fixed price calculation of multiple bundles issue
+* Fixed auto add required products for bundle items
+* Fixes #641: Protocol in sitemap should be HTTPS when `ForceSslForAllPages` is true
+* #640 Do not display shipping infos for grouped products in product lists
+* #634 Bundle price in list should not differ from price in details if there is only one bundle item with one attribute
+* Do not copy associated product when copying a bundle product
+* Null DeliveryTimeId when deleting products. Otherwise deleted products can prevent deletion of delivery times.
+* Fixed: Product with inactive attribute combination could be moved to wishlist, copied to cart and be purchased
+* Mobile Theme: Show prices according to selected variant value, display of deliverytime
+* Bundles: Display base price according to Catalog Setting > Product Detail > Display Base Price
+
+## SmartStore.NET 2.1.1
+
+### New Features
+* Html to PDF converter: PDF documents are created from regular HTML templates now, therefore radically simplifying PDF output customization.
+* Html widgets: added option to create a wrapper around widget content
+* SEO: added new settings `Canonical host name rule`. Enforces permanent redirection to a single domain name for a better page rank (e.g. myshop.com > www.myshop.com or vice versa)
+* SEO: added support for `` in multi-language stores. The tags are automatically rendered along with the language switcher.
+* (Developer) Implemented new HtmlHelper extension `AddLinkPart`: registers `` tags which should be rendered in page's head section
+* (Developer) Implemented new HtmlHelper extension `AddCustomHeadParts`: registers whatever head (meta) tag you wish
+* (Developer) Added `SmartUrlRoutingModule`, which can pass static files to `UrlRoutingModule` if desired (e.g. used by MiniProfiler). This way static files can be handled by regular actions or filters, without polluting web.config.
+* New payment plugin "Payone"
+* Option to set a delivery time for products available for order with stock quantity < 1
+* Option to disable product reviews on product detail page
+* Option to supress display of sub category picture links
+
+### Improvements
+* (Perf) Faster application warmup
+* (Perf) Faster product list rendering
+* Reworked routing: removed static file security barrier again (caused too much problems)
+* #545 Made all (applicable) settings multi-store-enabled
+* #579 Make all relative urls absolute prior sending email
+* The display order of attribute values are considered in the sorting of backend's attribute combination grid
+* Optimized error handling and redesigned error pages
+* Removed `PageNotFound` topic. Text is a simple locale resource now.
+* PayPal settings are multi-store-enabled
+* #555 Product edit: Improve category select box. Add history (last x selected items) above all others.
+* #510 Payment plugins: Qualify configuration(s) for multistores
+* #556 A negative value should be possible for additional payment fees
+* Dashboard: Order items linked with order list
+* Security: Missing http-only flag for some cookies set
+
+### Bugfixes
+* PayPal Express: corrected basket transmission by eliminating tax transmission
+* Fixed password recovery redirection
+* #552 Left navbar should stay expanded on product detail pages
+* #538 Specification attribute labels in product filter mask are not displayed localized
+* #540 Amazon payments: Multistore configuration might be lost if "All stores" are left empty
+* #532 Amazon payments: Reflect refunds made at amazon seller central when using data polling
+* #577 Exception thrown because of missing TaxRate table when opening tax by region provider configuration
+* Added IIS rewrite rule for `/productreviews/{id}` > `/product/reviews/{id}`
+* Email a friend redirects to "not found"
+* #567 Products marked as 'Featured' should also be included in regular lists
+* Fixed some missing company information in order to PDF export
+* #583 Fixed "The property rate with the value x is malformed" when creating products
+* Fixed ignored discount and tier price when product has attribute combination price
+* PayPal Standard provider now using shipping rather than billing address if shipping is required
+* Amazon payments: Order wasn't found if the capturing\refunding took place at Amazon Seller Central and the notification came through IPN
+
+
+## SmartStore.NET 2.1.0
+
+### New Features
+* (Developer) *Overhauled plugin architecture*:
- Plugins are regular MVC areas now
- No embedded views anymore. Views get copied to the deployment folder
- No cumbersome return View("Very.Long.View.Identifier") anymore
- Views in plugin source folders can be edited during debug. The changes are reflected without plugin recompilation.
-* Added _CDN_ setting to store configuration. Allows static files to be served through a content delivery network, e.g. cloudfront. (contributed by 'normalc')
+* (Developer) *Theme inheritance*: create derived child themes with minimum effort by overriding only small parts (static files and even variables).
+* *Preview Mode*: virtually test themes and stores more easily
+* New payment plugin *Pay with Amazon*
+* Support for *hierarchical SEO slugs*, e.g.: samsung/galaxy/s5/16gb/white
* (Developer) Model binding for plugin tab views: models from plugin tabs get automatically materialized and bound to TabbableModel.CustomProperties[MyKey]. Extended the SmartModelBinder for this.
* (Developer) New event _ModelBoundEvent_. Consume this in plugins to persist plugin specific models.
* (Admin) Added _GMC_ tab to product edit view. This is more a coding example for the above stuff rather than a new feature.
+* (Developer) Implemented _AdminThemedAttribute_: instructs the view engine to additionally search in the admin area for views. Very useful in larger plugin projects.
+* (Developer) Enhanced _IMenuProvider_: menu items can now be injected to the public catalog menu
* (Developer) Implemented _IWidgetProvider_. Allows request scoped registration of action routes to be injectable into widget zones. Perfect for custom action filters.
* (Developer) Simple widgets: the model of the parent action view context now gets passed to a widget.
* (Developer) New IoC method ContainerManager.InjectProperties()
+* Implemented support for EU VAT regulation for digital goods (2008/8/EG directive)
+* Implemented Media Manager for HTML editor (including file upload)
+* Added _CDN_ setting to store configuration. Allows static files to be served through a content delivery network, e.g. cloudfront. (contributed by 'normalc')
* #393 Web API: Implement OData actions for simpler working with product attributes
* #431 Web API: Add support for localized properties
* ShippingByWeight: new settings to configure a small quantity surcharge
@@ -27,9 +414,14 @@
* #266 Update Pending Order in Admin Panel
* #331 Show CommentBox in checkout (optional)
* Option to turn off the filter for products in categories
+* Export/Import was enabeled to work with localized values for name, short description and long description
+* Added two new themes 'Alpha Blue' and 'Alpha Black'
-###Improvements###
+### Improvements
* New backend design and cleaner frontend theme
+* Replaced TinyMCE HTML editor with CKeditor
+* Simplified checkout process by combining payment method and info pages
+* (Perf) Lower memory consumption
* (Perf) (Developer) Client Dependency updates
- jQuery 1.8.3 > 2.1.1 (although the backend is still using v1.8.3 because of the Telerik components)
- FontAwesome 3 > 4.1
@@ -45,7 +437,6 @@
- Shows last error
- (Developer) Breaking change: New parameter _TaskExecutionContext_ for _ITask.Execute()_
* UI: TabStrips remember their last selected tab across page requests in an unobtrusive way (removed old selection code)
-* TinyMCE 4: activated spell checking, added FontSelect and FontSizeSelect tools to the toolbar
* Price formatting: the DisplayLocale's FormatProvider was not applied when _CustomFormatting_ was specified for Currency
* Admin: Specification attributes are now sorted by DisplayOrder, THEN BY Name
* Admin: Replaced DatePicker with DateTimePicker control
@@ -71,8 +462,9 @@
* #375 Implement SKU search for 'related products picker'
* #391 Admin: allow searching/filtering for specification attributes
* Removed _OpenID_ plugin from core
+* Specification attribute values that are assigned to a product can be edited
-###Bugfixes###
+### Bugfixes
* Twitter Auth: fixed _SecurityTransparent_ error
* Facebook Auth: fixed _SecurityTransparent_ error
* OpenID Auth: fixed _SecurityTransparent_ error
@@ -95,11 +487,19 @@
* #419 email account password hidden
* #424 Localize return reasons & return actions
* #479 Product filter: Wrong count of manufacturers if products of sub-categories are included
+* #492 Ipayment credit card: Order notes are only created when the order exists
+* #493 Postfinance plugin does not work if shopname includes a "umlaut"
+* #237 Mobile theme: inactive attribute combinations should not be added to cart
+* #178 Mobile theme doesn't display base price
+* Ipayment: Capturing did not work because the security was not transmitted
+* #405 "Reset Password" Link in Emails is wrong with SSL secured sites
+* #471 Checkout: Redirecting to external payment page could take a while. Clicking "Buy" button again might cancel the redirecting.
+* Pricing not considered attribute combination prices for bundles with per item pricing
-##SmartStore.NET 2.0.2#
+## SmartStore.NET 2.0.2
-###Bugfixes###
+### Bugfixes
* IMPORTANT FIX: Memory leak leads to _OutOfMemoryException_ in application after a while
* Installation fix: some varchar(MAX) columns get created as varchar(4000). Added a migration to fix the column specs.
* Installation fix: Setup fails with exception _Value cannot be null. Parameter name: stream_
@@ -114,18 +514,18 @@
* Fixed "Controller does not implement IController" (concerning plugin controllers)
* #361 Wrong delivery time in order confirmation
-###Improvements###
+### Improvements
* #250 Implemented validation to theme configuration editing
-##SmartStore.NET 2.0.1##
+## SmartStore.NET 2.0.1
-###New Features###
+### New Features
* #292 Allow specific price for attribute combinations
* Added image upload support to Summernote editor
* (Developer) Added WebApi client test tools to the solution (C# and JavaScript)
-###Improvements###
+### Improvements
* Content slider slides can be filtered by store
* TinyMCE now shows advanced tab for images
* Updated BundleTransformer to the latest version 1.8.25
@@ -133,14 +533,14 @@
* Updated some 3rd party libraries to their latest versions
* #320 Unavailable attribute combinations: better UI indication
-###Bugfixes###
+### Bugfixes
* UI notifications get displayed repeatedly
* (Developer) Fixed Razor intellisense for plugin projects (NOTE: switch to 'PluginDev' configuration while editing plugin views, switch back when finished)
-##SmartStore.NET 2.0.0#
+## SmartStore.NET 2.0.0
-###Highlights###
+### Highlights
* RESTFul **WebApi**
* Highly optimized and **simplified product management**
* Product **Bundles**
@@ -150,7 +550,7 @@
* Lightning **fast asynchronous Excel import** with progress indicators and detailed reports
* (Developer) Code-based Entity Framework database **migrations**
-###New Features###
+### New Features
* [RESTFul WebApi](https://smartstore.codeplex.com/wikipage?title=Web%20API)
* Product Bundles: create configurable product kits by combining products
* Package upload for plugins and themes
@@ -171,7 +571,7 @@
* (Developer) New interface _IWidget_ for simplified widget development and multi-widgets per plugin
* (Developer) Outsourced notifications from MVC controller and implemented _INotifier_ for more centralized approach
-###Improvements###
+### Improvements
* Highly optimized and simplified product management
* Considerably faster app startup and page processing
* Lightning fast asynchronous Excel import with progress indicators and detailed reports
@@ -206,7 +606,7 @@
* * Updated all dependant libraries to their latest versions
* (Developer) Implemented _PaymentMethodBase_ abstract class to simplify payment plugin development
-###Bugfixes###
+### Bugfixes
* #150 GTB & disclaimer aren't readable when they become to long
* #151 NewsletterSubscriptionDeactivationUrl-Token doesn't get repleaced
* #153 Admin->Contentslider throws an error when asigned languages aren't available anymore
@@ -229,9 +629,9 @@
-##SmartStore.NET 1.2.1.0#
+## SmartStore.NET 1.2.1.0
-###New Features###
+### New Features
* Added option *Limit to current basket subtotal* to _HadSpentAmount_ discount rule
* Items in product lists can be labelled as _NEW_ for a configurable period of time
* Product templates can optionally display a _discount sign_ when discounts were applied
@@ -240,7 +640,7 @@
* Added a field for the HTML body id to store entity
* (Developer) New property 'ExtraData' for DiscountRequirement entity
-###Bugfixes###
+### Bugfixes
* #110: PictureService.GetThumbLocalPath doesn't add picture to cache if it doesn't exist (this broke PDFService among others)
* #114: Runtime error occurs after new customer registration in the storefront
* #115: XML exports could fail because of invalid characters
@@ -251,7 +651,7 @@
* #134: Fix mobile checkout
* #111: Send wishlist via e-mail doesn't work
-###Improvements###
+### Improvements
* #97: Product numbers of attribute combinations could not be searched
* #120: Excel product import: The same product pictures were imported repeatedly which led to duplicate pictures.
* Updated _FontAwesome_ to version 3.2.1
@@ -260,16 +660,16 @@
* Better display and handling when choosing a flag for languages
-##SmartStore.NET 1.2.0.0#
+## SmartStore.NET 1.2.0.0
-###Highlights###
+### Highlights
- Multi-store support
- "Trusted Shops" plugins
- Highly improved _SmartStore.biz Importer_ plugin
- Add custom HTML content to pages
- Performance optimization
-###New Features###
+### New Features
- **Multi-store-support:** now multiple stores can be managed within a single application instance (e.g. for building different catalogs, brands, landing pages etc.)
- Added 3 new **Trusted Shops** plugins: Seal, Buyer Protection, Store Reviews
- Added **Display as HTML Widget** to CMS Topics (store owner now can add arbitrary HTML content to any page region without modifying view files)
@@ -286,7 +686,7 @@
- (Developer) **Localizable views:** the view engine now is able to resolve localized (physical) view files (by appending the language seo code to a view file name in the same folder, e.g. 'en' or 'de'). The engine first tries to detect a view file with the matching language suffix, then falls back to the default one.
- (Developer) Added new interface __IPreApplicationStart__ allowing plugins to register HttpModules very early in the app bootstrap stage
-###Improvements###
+### Improvements
- Minor improvements for _SOFORT Überweisung_ plugin
- ContentSlider: updated 'sequence js' to most recent version and optimized html & css code
- Content slider: the background slide behaviour is configurable now (NoSlide, Slide, SlideOpposite)
@@ -304,7 +704,7 @@
- (Developer) Added 'bodyOnly' parameter to TopicBlock ChildAction
- (Developer) HtmlHelper __SmartLabelFor__ now renders the model property name by default (instead of the _SmartResourceDisplayName_ resource key)
-###Bugfixes###
+### Bugfixes
- bunch of fixes and improvements for the _SmartStore.biz Importer_ plugin
- The feed for "Leguide.com" plugin did not work in Germany
- Fixed minor issues in _shipping-by-weight_ plugin
@@ -319,9 +719,9 @@
-##SmartStore.NET 1.0.1.0##
+## SmartStore.NET 1.0.1.0
-###Bug###
+### Bug
* [SMNET-1] - Die Anzahl der eingetragenen Mengen bei Varianten wird nicht richtig im Warenkorb übernommen.
* [SMNET-5] - Fehler beim Hochladen von Bildern im IE
@@ -349,7 +749,7 @@
* [SMNET-199] - CategoryNavigationModel: Children von inaktiven Warengruppen müssen in Navigationsleisten ignoriert werden
* [SMNET-202] - SmartTabSelection mit verschachtelten Tabs fehlerhaft nach Reload einer Seite
-###Improvement###
+### 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)
@@ -374,7 +774,7 @@
* [SMNET-180] - Leichten Border und Verlauf in Lieferzeit-Indikator eingebaut
* [SMNET-188] - Lokalisierung: IsDirty-Flag und Option "Nur neue anfügen"
-###New Feature###
+### New Feature
* [SMNET-14] - Brutto/Netto Preisanzeige über Kundengruppen steuerbar
diff --git a/how-to-build.txt b/how-to-build.txt
index 102f51f3d9..7d0456ae8a 100644
--- a/how-to-build.txt
+++ b/how-to-build.txt
@@ -1,15 +1,14 @@
-Manually:
-1. SmartStore.Web and SmartStore.Admin are two web applications. They both need to be published to the same directory.
- SmartStore.Web to Published\Web\. SmartStore.Admin to Published\Web\Administration\. Make sure the .dlls
- from SmartStore.Admin (Published\Web\Administration\bin\) are moved and exist in (Published\Web\bin\).
-2. Ensure that plugins (\Presentation\SmartStore.Web\Plugins) are copied into \Published\Web\Plugins\ directory
-3. Select all the files in Published\Web\ directory and upload them to your web server.
+HOW TO BUILD:
+=============
+SmartStore.NET package with source code contains the "ClickToBuild.cmd" batch file, which will deploy everything correctly for you
+to a "Stage" folder. The build process will build the project in release mode and move all
+compiled and deployable files to the correct directory: "build/Web"
-Automatic:
-1. SmartStore.NET package with source code contains the "ClickToBuild.cmd" batch file, which will deploy everything correctly for you
- to a "Stage" folder. The build process will build the project in release mode and move all
- compiled and deployable files to the correct directory.
- Note: Before running the file make sure you built the project in Visual Studio.
-2. Select all the files in "build\Web" directory and upload them to your web server.
- Note: If you're getting an error, try to compile the solution in Visual Studio before running the .bat file
\ No newline at end of file
+SmartStore.NET source code distribution does NOT contain the NuGet packages required to succesfully build the solution.
+Instead these packages are downloaded during the very first compilation. Thus, when your initial build attempt fails,
+this could indicate that the packages could not be downloaded completely yet. In this case, just build again!
+If you're still getting errors, try to compile the solution in Visual Studio 2013 before running the .bat file, but
+before make sure that 'SmartStore.Web' is set as your startup project.
+
+After a successful build, select all the files in the "build\Web" directory and upload them to your web server.
\ No newline at end of file
diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll
new file mode 100644
index 0000000000..8acdf39c24
Binary files /dev/null and b/lib/SmartStore.Licensing/SmartStore.Licensing.dll differ
diff --git a/lib/Telerik/Telerik.Web.Mvc.dll b/lib/Telerik/Telerik.Web.Mvc.dll
new file mode 100644
index 0000000000..2d2710051c
Binary files /dev/null and b/lib/Telerik/Telerik.Web.Mvc.dll differ
diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe
index 8f61340295..c41a0d0deb 100644
Binary files a/src/.nuget/NuGet.exe and b/src/.nuget/NuGet.exe differ
diff --git a/src/AssemblySharedInfo.cs b/src/AssemblySharedInfo.cs
index 8fd027386d..79225de4c7 100644
--- a/src/AssemblySharedInfo.cs
+++ b/src/AssemblySharedInfo.cs
@@ -1,6 +1,4 @@
using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Security;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@@ -9,6 +7,6 @@
[assembly: AssemblyDescription("SmartStore.NET")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("SmartStore AG")]
-[assembly: AssemblyCopyright("Copyright © SmartStore AG 2014")]
+[assembly: AssemblyCopyright("Copyright © SmartStore AG 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs
index 3b7ecad06e..f74f0edf78 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.0.0.0")]
+[assembly: AssemblyVersion("2.6.0.0")]
-[assembly: AssemblyFileVersion("2.0.0.0")]
-[assembly: AssemblyInformationalVersion("2.0.2.0")]
+[assembly: AssemblyFileVersion("2.6.0.0")]
+[assembly: AssemblyInformationalVersion("2.6.0.0")]
diff --git a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
index 06733ed1ed..b02ea8dd58 100644
--- a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
+++ b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs
@@ -1,89 +1,372 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using System.Web.Hosting;
using Autofac;
using SmartStore.Core.Infrastructure;
-using SmartStore.Core.Infrastructure.DependencyManagement;
namespace SmartStore.Core.Async
{
public static class AsyncRunner
{
- private static readonly TaskFactory _myTaskFactory;
+ private static readonly BackgroundWorkHost _host = new BackgroundWorkHost();
- static AsyncRunner()
+ ///
+ /// Gets the global cancellation token which signals the application shutdown
+ ///
+ public static CancellationToken AppShutdownCancellationToken
{
- _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
+ get { return _host.ShutdownCancellationTokenSource.Token; }
}
+ ///
+ /// Executes an async Task method which has a void return value synchronously
+ ///
+ /// Task method to execute
public static void RunSync(Func func)
{
- _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
+ var oldContext = SynchronizationContext.Current;
+ var synch = new ExclusiveSynchronizationContext();
+ SynchronizationContext.SetSynchronizationContext(synch);
+ synch.Post(async _ =>
+ {
+ try
+ {
+ await func();
+ }
+ catch (Exception e)
+ {
+ synch.InnerException = e;
+ throw;
+ }
+ finally
+ {
+ synch.EndMessageLoop();
+ }
+ }, 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)
{
- return _myTaskFactory.StartNew>(func).Unwrap().GetAwaiter().GetResult();
+ var oldContext = SynchronizationContext.Current;
+ var synch = new ExclusiveSynchronizationContext();
+ SynchronizationContext.SetSynchronizationContext(synch);
+ TResult ret = default(TResult);
+ synch.Post(async _ =>
+ {
+ try
+ {
+ ret = await func();
+ }
+ catch (Exception e)
+ {
+ synch.InnerException = e;
+ throw;
+ }
+ finally
+ {
+ synch.EndMessageLoop();
+ }
+ }, null);
+ synch.BeginMessageLoop();
+ SynchronizationContext.SetSynchronizationContext(oldContext);
+ return ret;
}
- public static Task Run(Action action)
+ public static Task Run(Action action)
{
return Run(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
}
- public static Task Run(Action action, CancellationToken cancellationToken)
+ public static Task Run(Action action, CancellationToken cancellationToken)
{
return Run(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
}
- public static Task Run(Action action, TaskCreationOptions options)
+ public static Task Run(Action action, TaskCreationOptions options)
{
return Run(action, CancellationToken.None, options, TaskScheduler.Default);
}
- public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options)
+ public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options)
{
return Run(action, cancellationToken, options, TaskScheduler.Default);
}
- public static Task Run(Action action, TaskScheduler scheduler)
+ public static Task Run(Action action, TaskScheduler scheduler)
{
return Run(action, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
-
- public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
+
+ public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
{
Guard.ArgumentNotNull(() => action);
Guard.ArgumentNotNull(() => scheduler);
+ var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
+ options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
+
var t = Task.Factory.StartNew(() => {
- using (var container = EngineContext.Current.ContainerManager.Container.BeginLifetimeScope(AutofacLifetimeScopeProvider.HttpRequestTag))
+ var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
+ using (accessor.BeginContextAwareScope())
{
- action(container);
+ action(accessor.GetLifetimeScope(null), ct);
}
- }, cancellationToken, options, scheduler);
+ }, ct, options, scheduler);
+
+ _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, TaskCreationOptions options, TaskScheduler scheduler)
{
Guard.ArgumentNotNull(() => state);
Guard.ArgumentNotNull(() => action);
Guard.ArgumentNotNull(() => scheduler);
+ var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
+ options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
+
+ var t = Task.Factory.StartNew((o) =>
+ {
+ var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
+ using (accessor.BeginContextAwareScope())
+ {
+ action(accessor.GetLifetimeScope(null), ct, o);
+ }
+ }, state, ct, options, scheduler);
+
+ _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)
+ {
+ Guard.ArgumentNotNull(() => function);
+ Guard.ArgumentNotNull(() => 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;
+ using (accessor.BeginContextAwareScope())
+ {
+ return function(accessor.GetLifetimeScope(null), ct);
+ }
+ }, ct, options, scheduler);
+
+ _host.Register(t, ct);
+
+ return t;
+ }
+
+ public static Task Run(Func function, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
+ {
+ Guard.ArgumentNotNull(() => state);
+ Guard.ArgumentNotNull(() => function);
+ Guard.ArgumentNotNull(() => scheduler);
+
+ var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
+ options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
+
var t = Task.Factory.StartNew((o) =>
{
- using (var container = EngineContext.Current.ContainerManager.Container.BeginLifetimeScope(AutofacLifetimeScopeProvider.HttpRequestTag))
+ var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
+ using (accessor.BeginContextAwareScope())
{
- action(container, o);
+ return function(accessor.GetLifetimeScope(null), ct, o);
}
- }, state, cancellationToken, options, scheduler);
+ }, state, ct, options, scheduler);
+
+ _host.Register(t, ct);
return t;
}
+
+ private class ExclusiveSynchronizationContext : SynchronizationContext
+ {
+ private bool _done;
+ readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false);
+ readonly Queue> _items = new Queue>();
+
+ public Exception InnerException { get; set; }
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ throw new NotSupportedException("We cannot send to the same thread");
+ }
+
+ public override void Post(SendOrPostCallback d, object state)
+ {
+ lock (_items)
+ {
+ _items.Enqueue(Tuple.Create(d, state));
+ }
+ _workItemsWaiting.Set();
+ }
+
+ public void EndMessageLoop()
+ {
+ Post(_ => _done = true, null);
+ }
+
+ public void BeginMessageLoop()
+ {
+ while (!_done)
+ {
+ Tuple task = null;
+ lock (_items)
+ {
+ if (_items.Count > 0)
+ {
+ task = _items.Dequeue();
+ }
+ }
+ if (task != null)
+ {
+ task.Item1(task.Item2);
+ if (InnerException != null) // the method threw an exeption
+ {
+ throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
+ }
+ }
+ else
+ {
+ _workItemsWaiting.WaitOne();
+ }
+ }
+ }
+
+ public override SynchronizationContext CreateCopy()
+ {
+ return this;
+ }
+ }
+
+ }
+
+ internal class BackgroundWorkHost : IRegisteredObject
+ {
+ private readonly CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource();
+ private int _numRunningWorkItems;
+
+ public BackgroundWorkHost()
+ {
+ HostingEnvironment.RegisterObject(this);
+ }
+
+ public CancellationTokenSource ShutdownCancellationTokenSource
+ {
+ get { return _shutdownCancellationTokenSource; }
+ }
+
+ public void Stop(bool immediate)
+ {
+ int num;
+ lock (this)
+ {
+ _shutdownCancellationTokenSource.Cancel();
+ num = _numRunningWorkItems;
+ }
+ if (num == 0)
+ {
+ FinalShutdown();
+ }
+ }
+
+ public CancellationTokenSource CreateCompositeCancellationTokenSource(CancellationToken userCancellationToken)
+ {
+ if (userCancellationToken == CancellationToken.None)
+ {
+ return _shutdownCancellationTokenSource;
+ }
+ return CancellationTokenSource.CreateLinkedTokenSource(_shutdownCancellationTokenSource.Token, userCancellationToken);
+ }
+
+ public void Register(Task work, CancellationToken cancellationToken)
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ lock (this)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+ _numRunningWorkItems++;
+ }
+
+ work.ContinueWith(
+ WorkItemComplete,
+ CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously,
+ TaskScheduler.Default);
+ }
+ }
+
+ private void WorkItemComplete(Task work)
+ {
+ int num;
+ bool isCancellationRequested;
+ lock (this)
+ {
+ num = --_numRunningWorkItems;
+ isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested;
+ }
+ if (num == 0 && isCancellationRequested)
+ {
+ FinalShutdown();
+ }
+ }
+
+ private void FinalShutdown()
+ {
+ HostingEnvironment.UnregisterObject(this);
+ }
+
}
}
diff --git a/src/Libraries/SmartStore.Core/Async/AsyncState.cs b/src/Libraries/SmartStore.Core/Async/AsyncState.cs
index 2a4fb4a1d9..ce99542612 100644
--- a/src/Libraries/SmartStore.Core/Async/AsyncState.cs
+++ b/src/Libraries/SmartStore.Core/Async/AsyncState.cs
@@ -1,9 +1,6 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Linq;
using System.Runtime.Caching;
-using System.Text;
using System.Threading;
namespace SmartStore.Core.Async
@@ -11,8 +8,8 @@ namespace SmartStore.Core.Async
public class AsyncState
{
- private readonly static AsyncState s_instance = new AsyncState();
- private readonly MemoryCache _cache = MemoryCache.Default;
+ private static readonly AsyncState s_instance = new AsyncState();
+ private readonly MemoryCache _cache = new MemoryCache("SmartStore.AsyncState");
private AsyncState()
{
@@ -35,6 +32,22 @@ public T Get(string name = null)
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;
@@ -46,13 +59,13 @@ private T Get(out CancellationTokenSource cancelTokenSource, string name = nu
{
cancelTokenSource = null;
var key = BuildKey(name);
-
+
var value = _cache.Get(key) as StateInfo;
if (value != null)
{
cancelTokenSource = value.CancellationTokenSource;
- return (T)(value.Progress);
+ return (T)value.Progress;
}
return default(T);
@@ -60,14 +73,32 @@ private T Get(out CancellationTokenSource cancelTokenSource, string name = nu
public void Set(T state, string name = null, bool neverExpires = false)
{
- this.Set(state, null, name, neverExpires);
+ Guard.ArgumentNotNull(() => state);
+ Set(state, null, name, neverExpires);
+ }
+
+ public void Update(Action update, string name = null)
+ {
+ Guard.ArgumentNotNull(() => 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.ArgumentNotNull(() => cancelTokenSource);
- this.Set(default(T), cancelTokenSource, name);
+ Set(default(T), cancelTokenSource, name);
}
private void Set(T state, CancellationTokenSource cancelTokenSource, string name = null, bool neverExpires = false)
@@ -79,7 +110,10 @@ private void Set(T state, CancellationTokenSource cancelTokenSource, string n
if (value != null)
{
// exists already, so update
- value.Progress = state;
+ if (state != null)
+ {
+ value.Progress = state;
+ }
if (cancelTokenSource != null && value.CancellationTokenSource == null)
{
value.CancellationTokenSource = cancelTokenSource;
@@ -88,7 +122,10 @@ private void Set(T state, CancellationTokenSource cancelTokenSource, string n
var policy = new CacheItemPolicy { SlidingExpiration = neverExpires ? TimeSpan.Zero : TimeSpan.FromMinutes(15) };
- _cache.Set(key, value ?? new StateInfo { Progress = state, CancellationTokenSource = cancelTokenSource }, policy);
+ _cache.Set(
+ key,
+ value ?? new StateInfo { Progress = state, CancellationTokenSource = cancelTokenSource },
+ policy);
}
public bool Remove(string name = null)
diff --git a/src/Libraries/SmartStore.Core/BaseEntity.cs b/src/Libraries/SmartStore.Core/BaseEntity.cs
index f41a4f97c1..0c90dc9a9a 100644
--- a/src/Libraries/SmartStore.Core/BaseEntity.cs
+++ b/src/Libraries/SmartStore.Core/BaseEntity.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
@@ -12,24 +13,16 @@ namespace SmartStore.Core
///
[DataContract]
public abstract partial class BaseEntity
- {
- ///
+ {
+ ///
/// Gets or sets the entity identifier
///
- [DataMember, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ [DataMember]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
- public override bool Equals(object obj)
- {
- return Equals(obj as BaseEntity);
- }
-
- private static bool IsTransient(BaseEntity obj)
- {
- return obj != null && Equals(obj.Id, default(int));
- }
-
- public Type GetUnproxiedType()
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ public Type GetUnproxiedType()
{
var t = GetType();
if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity."))
@@ -40,7 +33,21 @@ public Type GetUnproxiedType()
return t;
}
- public virtual bool Equals(BaseEntity other)
+ ///
+ /// 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()
+ {
+ return Id == 0;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as BaseEntity);
+ }
+
+ protected virtual bool Equals(BaseEntity other)
{
if (other == null)
return false;
@@ -48,24 +55,34 @@ public virtual bool Equals(BaseEntity other)
if (ReferenceEquals(this, other))
return true;
- if (!IsTransient(this) &&
- !IsTransient(other) &&
- Equals(Id, other.Id))
+ if (HasSameNonDefaultIds(other))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
- return thisType.IsAssignableFrom(otherType) ||
- otherType.IsAssignableFrom(thisType);
+ return thisType.Equals(otherType);
}
return false;
}
- public override int GetHashCode()
+ [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
+ public override int GetHashCode()
{
- if (Equals(Id, default(int)))
- return base.GetHashCode();
- return Id.GetHashCode();
+ if (IsTransientRecord())
+ {
+ return base.GetHashCode();
+ }
+ else
+ {
+ unchecked
+ {
+ // It's possible for two objects to return the same hash code based on
+ // identically valued properties, even if they're of two different types,
+ // so we include the object's type in the hash calculation
+ var hashCode = GetUnproxiedType().GetHashCode();
+ return (hashCode * 31) ^ Id.GetHashCode();
+ }
+ }
}
public static bool operator ==(BaseEntity x, BaseEntity y)
@@ -77,5 +94,10 @@ public override int GetHashCode()
{
return !(x == y);
}
+
+ private bool HasSameNonDefaultIds(BaseEntity other)
+ {
+ return !this.IsTransientRecord() && !other.IsTransientRecord() && this.Id == other.Id;
+ }
}
}
diff --git a/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs b/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs
index 0c3bb4d9a2..4af855729a 100644
--- a/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs
@@ -4,81 +4,80 @@
using System.Linq;
using System.Web;
using System.Web.Caching;
-using SmartStore.Core.Fakes;
namespace SmartStore.Core.Caching
{
public partial class AspNetCache : ICache
{
- private const string REGION_NAME = "$$SmartStoreNET$$";
- private readonly HttpContextBase _context;
-
- public AspNetCache(HttpContextBase context)
- {
- this._context = context;
- }
+ private const string RegionName = "$$SmartStoreNET$$";
public IEnumerable> Entries
{
get
{
- if (_context is FakeHttpContext)
+ if (HttpRuntime.Cache == null)
return Enumerable.Empty>();
- return from entry in _context.Cache.Cast()
+ return from entry in HttpRuntime.Cache.Cast()
let key = entry.Key.ToString()
- where key.StartsWith(REGION_NAME)
+ where key.StartsWith(RegionName)
select new KeyValuePair(
- key.Substring(REGION_NAME.Length),
+ key.Substring(RegionName.Length),
entry.Value);
}
}
public object Get(string key)
{
- if (_context is FakeHttpContext)
+ if (HttpRuntime.Cache == null)
return null;
-
- return _context.Cache.Get(BuildKey(key));
+
+ return HttpRuntime.Cache.Get(BuildKey(key));
}
public void Set(string key, object value, int? cacheTime)
{
+ if (HttpRuntime.Cache == null)
+ return;
+
key = BuildKey(key);
var absoluteExpiration = Cache.NoAbsoluteExpiration;
- if (cacheTime.GetValueOrDefault() > 0)
+ if (cacheTime.HasValue)
{
+ var span = cacheTime.Value == 0 ? TimeSpan.FromMilliseconds(10) : TimeSpan.FromMinutes(cacheTime.Value);
absoluteExpiration = DateTime.UtcNow + TimeSpan.FromMinutes(cacheTime.Value);
}
- _context.Cache.Insert(key, value, null, absoluteExpiration, Cache.NoSlidingExpiration);
+ HttpRuntime.Cache.Insert(key, value, null, absoluteExpiration, Cache.NoSlidingExpiration);
}
public bool Contains(string key)
{
- if (_context is FakeHttpContext)
+ if (HttpRuntime.Cache == null)
return false;
-
- return _context.Cache.Get(BuildKey(key)) != null;
+
+ return HttpRuntime.Cache.Get(BuildKey(key)) != null;
}
public void Remove(string key)
{
- if (_context is FakeHttpContext)
+ if (HttpRuntime.Cache == null)
return;
-
- _context.Cache.Remove(BuildKey(key));
+
+ HttpRuntime.Cache.Remove(BuildKey(key));
}
public static string BuildKey(string key)
{
- return key.HasValue() ? REGION_NAME + key : null;
+ return key.HasValue() ? RegionName + key : null;
}
public bool IsSingleton
{
+ // because Asp.NET Cache is thread-safe by itself,
+ // no need to mess up with locks.
get { return false; }
}
diff --git a/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs b/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs
index bdf35da722..6949aec542 100644
--- a/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs
+++ b/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs
@@ -12,7 +12,7 @@ public static class ICacheManagerExtensions
{
public static T Get(this ICacheManager cacheManager, string key)
{
- return cacheManager.Get(key, () => { return default(T); });
+ return cacheManager.Get(key, () => default(T));
}
}
@@ -21,7 +21,12 @@ public partial class CacheManager : ICacheManager where TCache : ICache
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private readonly ICache _cache;
- public CacheManager(Func fn)
+ // 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.
+ private const string FakeNull = "__[NULL]__";
+
+ public CacheManager(Func fn)
{
this._cache = fn(typeof(TCache));
}
@@ -32,35 +37,40 @@ public T Get(string key, Func acquirer, int? cacheTime = null)
if (_cache.Contains(key))
{
- return (T)_cache.Get(key);
+ return GetExisting(key);
}
- else
+
+ using (EnterReadLock())
{
- using (EnterReadLock())
+ if (!_cache.Contains(key))
{
- if (!_cache.Contains(key))
- {
- var value = acquirer();
- this.Set(key, value, cacheTime);
+ var value = acquirer();
+ this.Set(key, value, cacheTime);
- return value;
- }
+ return value;
}
-
- return (T)_cache.Get(key);
}
- }
+
+ return GetExisting(key);
+ }
+
+ private T GetExisting(string key)
+ {
+ var value = _cache.Get(key);
+
+ if (value.Equals(FakeNull))
+ return default(T);
+
+ return (T)_cache.Get(key);
+ }
public void Set(string key, object value, int? cacheTime = null)
{
Guard.ArgumentNotEmpty(() => key);
-
- if (value == null)
- return;
using (EnterWriteLock())
{
- _cache.Set(key, value, cacheTime);
+ _cache.Set(key, value ?? FakeNull, cacheTime);
}
}
diff --git a/src/Libraries/SmartStore.Core/Caching/ICache.cs b/src/Libraries/SmartStore.Core/Caching/ICache.cs
index a250d20bc0..b9fc134a8d 100644
--- a/src/Libraries/SmartStore.Core/Caching/ICache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/ICache.cs
@@ -26,7 +26,7 @@ public interface ICache
/// Adds the cache item with the specified key
///
/// Key
- /// Data
+ /// Data
/// Cache time in minutes
void Set(string key, object value, int? cacheTime);
diff --git a/src/Libraries/SmartStore.Core/Caching/NullCache.cs b/src/Libraries/SmartStore.Core/Caching/NullCache.cs
index ea96b38717..f02414d682 100644
--- a/src/Libraries/SmartStore.Core/Caching/NullCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/NullCache.cs
@@ -7,8 +7,7 @@ namespace SmartStore.Core.Caching
///
public partial class NullCache : ICacheManager
{
-
- private readonly static ICacheManager s_instance = new NullCache();
+ private static readonly ICacheManager s_instance = new NullCache();
public static ICacheManager Instance
{
diff --git a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs
index a293c6cd4f..7ab183061f 100644
--- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs
@@ -9,7 +9,7 @@ namespace SmartStore.Core.Caching
public partial class RequestCache : ICache
{
- private const string REGION_NAME = "$$SmartStoreNET$$";
+ private const string RegionName = "$$SmartStoreNET$$";
private readonly HttpContextBase _context;
public RequestCache(HttpContextBase context)
@@ -39,9 +39,9 @@ public IEnumerable> Entries
string key = enumerator.Key as string;
if (key == null)
continue;
- if (key.StartsWith(REGION_NAME))
+ if (key.StartsWith(RegionName))
{
- yield return new KeyValuePair(key.Substring(REGION_NAME.Length), enumerator.Value);
+ yield return new KeyValuePair(key.Substring(RegionName.Length), enumerator.Value);
}
}
}
@@ -64,13 +64,10 @@ public void Set(string key, object value, int? cacheTime)
key = BuildKey(key);
- if (value != null)
- {
- if (items.Contains(key))
- items[key] = value;
- else
- items.Add(key, value);
- }
+ if (items.Contains(key))
+ items[key] = value;
+ else
+ items.Add(key, value);
}
public bool Contains(string key)
@@ -93,7 +90,7 @@ public void Remove(string key)
private string BuildKey(string key)
{
- return key.HasValue() ? REGION_NAME + key : null;
+ return key.HasValue() ? RegionName + key : null;
}
public bool IsSingleton
diff --git a/src/Libraries/SmartStore.Core/Caching/StaticCache.cs b/src/Libraries/SmartStore.Core/Caching/StaticCache.cs
index 3e68892de3..c4fc216e76 100644
--- a/src/Libraries/SmartStore.Core/Caching/StaticCache.cs
+++ b/src/Libraries/SmartStore.Core/Caching/StaticCache.cs
@@ -42,9 +42,10 @@ public void Set(string key, object value, int? cacheTime)
{
var cacheItem = new CacheItem(key, value);
CacheItemPolicy policy = null;
- if (cacheTime.GetValueOrDefault() > 0)
+ if (cacheTime.HasValue)
{
- policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime.Value) };
+ var span = cacheTime.Value == 0 ? TimeSpan.FromMilliseconds(10) : TimeSpan.FromMinutes(cacheTime.Value);
+ policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now + span };
}
Cache.Add(cacheItem, policy);
diff --git a/src/Libraries/SmartStore.Core/Collections/IPersistentCollection.cs b/src/Libraries/SmartStore.Core/Collections/IPersistentCollection.cs
deleted file mode 100644
index c536842b73..0000000000
--- a/src/Libraries/SmartStore.Core/Collections/IPersistentCollection.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-
-namespace SmartStore.Collections
-{
- public interface IPersistentCollection : ICollection, ICollection
- where T : class
- {
- Action> AfterAdd { get; set; }
- Action> AfterRemove { get; set; }
- Func, T, bool> BeforeAdd { get; set; }
- Func, T, bool> BeforeRemove { get; set; }
- }
-}
diff --git a/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs
new file mode 100644
index 0000000000..789c363da0
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+
+namespace SmartStore.Collections
+{
+ ///
+ /// Manages data keys and offers a combination of eager and lazy data loading
+ ///
+ public class LazyMultimap : Multimap
+ {
+ private readonly Func> _load;
+ private readonly List _loaded; // to avoid database round trips with empty results
+ private List _collect;
+ //private int _roundTripCount;
+
+ ///
+ /// Constructor
+ ///
+ /// int[] keys like Entity.Id, Multimap{int, T}> delegate to load data
+ /// Keys of eager loaded data
+ public LazyMultimap(Func> load, IEnumerable collect = null)
+ {
+ _load = load;
+ _loaded = new List();
+
+ _collect = collect == null ? new List() : new List(collect);
+ }
+
+ protected virtual void Load(IEnumerable keys)
+ {
+ if (keys != null)
+ {
+ var loadKeys = (_collect.Count == 0 ? keys : _collect.Concat(keys))
+ .Distinct()
+ .Except(_loaded)
+ .ToArray();
+
+ _collect.Clear(); // invalidate, do not load again
+
+ if (loadKeys.Any())
+ {
+ //++_roundTripCount;
+ //Debug.WriteLine("Round trip {0} of {1}: {2}", _roundTripCount, typeof(T).Name, string.Join(",", loadKeys.OrderBy(x => x)));
+
+ var items = _load(loadKeys);
+
+ _loaded.AddRange(loadKeys);
+
+ if (items != null)
+ {
+ foreach (var range in items)
+ {
+ base.AddRange(range.Key, range.Value);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get data. Load it if not already loaded yet.
+ ///
+ /// Data key
+ /// Collection of data
+ public virtual ICollection Load(int key)
+ {
+ if (key == 0)
+ {
+ return new List();
+ }
+
+ if (!_loaded.Contains(key))
+ {
+ Load(new int[] { key });
+ }
+
+ // better not override indexer cause of stack overflow risk
+ var result = base[key];
+
+ Debug.Assert(_loaded.Contains(key), "Possible missing multimap result for key {0} and type {1}.".FormatInvariant(key, typeof(T).Name), "");
+
+ return result;
+ }
+
+ ///
+ /// Collect keys for combined loading
+ ///
+ /// Data keys
+ [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
+ public virtual void Collect(IEnumerable keys)
+ {
+ if (keys != null && keys.Any())
+ {
+ _collect = _collect.Union(keys).ToList();
+ }
+ }
+
+ ///
+ /// Collect single key for combined loading
+ ///
+ /// Data key
+ public virtual void Collect(int key)
+ {
+ if (key != 0 && !_collect.Contains(key))
+ {
+ _collect.Add(key);
+ }
+ }
+
+ public override void Clear()
+ {
+ _loaded.Clear();
+ _collect.Clear();
+ //_roundTripCount = 0;
+
+ base.Clear();
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs
new file mode 100644
index 0000000000..6d81d0bc3a
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs
@@ -0,0 +1,101 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SmartStore.Collections
+{
+ public class MostRecentlyUsedList : IEnumerable
+ {
+ private readonly int _maxSize;
+ private readonly List _mru;
+
+ public MostRecentlyUsedList(int maxSize)
+ {
+ _maxSize = maxSize;
+ _mru = new List();
+ }
+
+ public MostRecentlyUsedList(IEnumerable collection, int maxSize)
+ {
+ _maxSize = maxSize;
+ _mru = collection.ToList();
+
+ Normalize();
+ }
+
+ public MostRecentlyUsedList(string collection, int maxSize)
+ {
+ _maxSize = maxSize;
+ _mru = collection.SplitSafe(Delimiter).Cast().ToList();
+
+ Normalize();
+ }
+
+ public MostRecentlyUsedList(IEnumerable collection, T newItem, int maxSize)
+ {
+ _maxSize = maxSize;
+ _mru = collection.ToList();
+
+ Add(newItem);
+ }
+
+ public MostRecentlyUsedList(string collection, T newItem, int maxSize)
+ {
+ _maxSize = maxSize;
+ _mru = collection.SplitSafe(Delimiter).Cast().ToList();
+
+ Add(newItem);
+ }
+
+ public static string Delimiter { get { return ";"; } }
+
+ private void Normalize()
+ {
+ if (_maxSize >= 0)
+ {
+ while (_mru.Count > _maxSize)
+ _mru.RemoveAt(_mru.Count - 1);
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _mru.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _mru.GetEnumerator();
+ }
+
+ public override string ToString()
+ {
+ return string.Join(Delimiter, _mru);
+ }
+
+ public T this[int key]
+ {
+ get
+ {
+ return _mru[key];
+ }
+ set
+ {
+ _mru[key] = value;
+ }
+ }
+
+ public int Count { get { return _mru.Count; } }
+
+ public void Add(T item)
+ {
+ int i = _mru.IndexOf(item);
+ if (i > -1)
+ _mru.RemoveAt(i);
+
+ _mru.Insert(0, item);
+
+ Normalize();
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Collections/MultiMap.cs b/src/Libraries/SmartStore.Core/Collections/MultiMap.cs
index 3e20b70a5d..1ae76884f8 100644
--- a/src/Libraries/SmartStore.Core/Collections/MultiMap.cs
+++ b/src/Libraries/SmartStore.Core/Collections/MultiMap.cs
@@ -16,10 +16,10 @@ namespace SmartStore.Collections
///
/// The type of key.
/// The type of value.
- public class Multimap : IEnumerable>>
+ public class Multimap : IEnumerable>>
{
- private readonly IDictionary> _items;
- private readonly Func> _listCreator;
+ private readonly IDictionary> _items;
+ private readonly Func> _listCreator;
private readonly bool _isReadonly = false;
public Multimap()
@@ -31,29 +31,29 @@ internal Multimap(bool threadSafe)
{
if (threadSafe)
{
- _items = new ConcurrentDictionary>();
- _listCreator = () => new ThreadSafeList();
+ _items = new ConcurrentDictionary>();
+ _listCreator = () => new SynchronizedCollection();
}
else
{
- _items = new Dictionary>();
+ _items = new Dictionary>();
_listCreator = () => new List();
}
}
- public Multimap(Func> listCreator)
- : this(new Dictionary>(), listCreator)
+ public Multimap(Func> listCreator)
+ : this(new Dictionary>(), listCreator)
{
}
- internal Multimap(IDictionary> dictionary, Func> listCreator)
+ internal Multimap(IDictionary> dictionary, Func> listCreator)
{
_items = dictionary;
_listCreator = listCreator;
}
- protected Multimap(IDictionary> dictionary, bool isReadonly)
+ protected Multimap(IDictionary> dictionary, bool isReadonly)
{
Guard.ArgumentNotNull(() => dictionary);
@@ -96,7 +96,7 @@ public int TotalValueCount
/// Gets the collection of values stored under the specified key.
///
/// The key.
- public virtual IList this[TKey key]
+ public virtual ICollection this[TKey key]
{
get
{
@@ -123,7 +123,7 @@ public virtual ICollection Keys
///
/// Gets the collection of collections of values.
///
- public virtual ICollection> Values
+ public virtual ICollection> Values
{
get { return _items.Values; }
}
@@ -239,9 +239,9 @@ IEnumerator IEnumerable.GetEnumerator()
/// Returns an enumerator that iterates through the multimap.
///
/// An object that can be used to iterate through the multimap.
- public virtual IEnumerator>> GetEnumerator()
+ public virtual IEnumerator>> GetEnumerator()
{
- foreach (KeyValuePair> pair in _items)
+ foreach (KeyValuePair> pair in _items)
yield return pair;
}
@@ -273,63 +273,5 @@ public static Multimap CreateFromLookup(ILookup sour
}
#endregion
-
- #region Nested ThreadSafeMultimap
-
- //private class ThreadSafeMultimap : Multimap
- //{
- // private readonly ReaderWriterLockSlim _rwLock;
-
- // public ThreadSafeMultimap(ReaderWriterLockSlim rwLock)
- // {
- // _rwLock = rwLock;
- // }
-
- // public override void Add(TKey key, TValue value)
- // {
- // using (_rwLock.GetUpgradeableReadLock())
- // {
- // base.Add(key, value);
- // }
- // }
-
- // public override void AddRange(TKey key, IEnumerable values)
- // {
- // using (_rwLock.GetUpgradeableReadLock())
- // {
- // base.AddRange(key, values);
- // }
- // }
-
- // public override bool ContainsValue(TKey key, TValue value)
- // {
- // using (_rwLock.GetUpgradeableReadLock())
- // {
- // return base.ContainsValue(key, value);
- // }
- // }
-
- // public override bool Remove(TKey key, TValue value)
- // {
- // using (_rwLock.GetUpgradeableReadLock())
- // {
- // return base.Remove(key, value);
- // }
- // }
-
- // public override IList this[TKey key]
- // {
- // get
- // {
- // using (_rwLock.GetUpgradeableReadLock())
- // {
- // return base[key];
- // }
- // }
- // }
-
- //}
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/Libraries/SmartStore.Core/Collections/PersistentCollection.cs b/src/Libraries/SmartStore.Core/Collections/PersistentCollection.cs
deleted file mode 100644
index 6322bd0b1f..0000000000
--- a/src/Libraries/SmartStore.Core/Collections/PersistentCollection.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace SmartStore.Collections
-{
- public class PersistentCollection : IPersistentCollection
- where T : class
- {
- private readonly ICollection _actual;
- private Action> _afterAdd;
- private Action> _afterRemove;
- private Func, T, bool> _beforeAdd;
- private Func, T, bool> _beforeRemove;
-
- public PersistentCollection(ICollection actual)
- {
- _actual = actual;
- }
-
- ///
- /// perform actions on one or more list items after an item is
- /// added.
- ///
- public Action> AfterAdd
- {
- get { return _afterAdd ?? (_afterAdd = l => { }); }
- set { _afterAdd = value; }
- }
-
- ///
- /// perform actions on one or more list items after an item is
- /// removed.
- ///
- public Action> AfterRemove
- {
- get { return _afterRemove ?? (_afterRemove = l => { }); }
- set { _afterRemove = value; }
- }
-
- ///
- /// perform a check on the item being added before adding it.
- /// Return true if it should be added, false if it should not be
- /// added.
- ///
- public Func, T, bool> BeforeAdd
- {
- get { return _beforeAdd ?? (_beforeAdd = (l, x) => true); }
- set { _beforeAdd = value; }
- }
-
- ///
- /// perform a check on the item being removed before removing
- /// it. Return true if it should be removed, false if it should not
- /// be removed.
- ///
- public Func, T, bool> BeforeRemove
- {
- get { return _beforeRemove ?? (_beforeRemove = (l, x) => true); }
- set { _beforeRemove = value; }
- }
-
- public IEnumerator GetEnumerator()
- {
- return _actual.GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
-
- public int Count
- {
- get { return _actual.Count; }
- }
- public bool IsReadOnly
- {
- get { return _actual.IsReadOnly; }
- }
-
- public void Add(T item)
- {
- if (BeforeAdd(this, item))
- {
- _actual.Add(item);
- AfterAdd(this);
- }
- }
-
- public void Clear()
- {
- while (_actual.Any())
- {
- Remove(_actual.First());
- }
- }
-
- public bool Contains(T item)
- {
- return _actual.Contains(item);
- }
-
- public void CopyTo(T[] array, int arrayIndex)
- {
- _actual.CopyTo(array, arrayIndex);
- }
-
- public bool Remove(T item)
- {
- if (BeforeRemove(this, item))
- {
- bool toReturn = _actual.Remove(item);
- AfterRemove(this);
- return toReturn;
- }
- return true;
- }
-
- void ICollection.CopyTo(Array array, int index)
- {
- var copy = new T[_actual.Count];
- _actual.CopyTo(copy, 0);
- Array.Copy(copy, 0, array, index, _actual.Count);
- }
-
- int ICollection.Count
- {
- get { return _actual.Count; }
- }
-
- bool ICollection.IsSynchronized
- {
- get { throw new NotImplementedException(); }
- }
-
- object ICollection.SyncRoot
- {
- get { throw new NotImplementedException(); }
- }
- }
-}
diff --git a/src/Libraries/SmartStore.Core/Collections/Querystring.cs b/src/Libraries/SmartStore.Core/Collections/Querystring.cs
new file mode 100644
index 0000000000..9df23fc868
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Collections/Querystring.cs
@@ -0,0 +1,198 @@
+using System.Text;
+using System.Web;
+using System.Collections.Specialized;
+using System.Diagnostics.CodeAnalysis;
+
+namespace SmartStore.Collections
+{
+ ///
+ /// http://weblogs.asp.net/bradvincent/archive/2008/10/27/helper-class-querystring-builder-chainable.aspx
+ ///
+ public class QueryString : NameValueCollection
+ {
+ public QueryString()
+ {
+ }
+
+ public QueryString(string queryString)
+ {
+ FillFromString(queryString);
+ }
+
+ public static QueryString Current
+ {
+ get { return new QueryString().FromCurrent(); }
+ }
+
+ ///
+ /// extracts a querystring from a full URL
+ ///
+ /// the string to extract the querystring from
+ /// a string representing only the querystring
+ [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
+ public static string ExtractQuerystring(string s)
+ {
+ if (!string.IsNullOrEmpty(s))
+ {
+ if (s.Contains("?"))
+ {
+ return s.Substring(s.IndexOf("?") + 1);
+ }
+ }
+ return s;
+ }
+
+ ///
+ /// returns a querystring object based on a string
+ ///
+ /// the string to parse
+ /// the QueryString object
+ public QueryString FillFromString(string s, bool urlDecode = false)
+ {
+ base.Clear();
+ if (string.IsNullOrEmpty(s))
+ {
+ return this;
+ }
+
+ foreach (string keyValuePair in ExtractQuerystring(s).Split('&'))
+ {
+ if (string.IsNullOrEmpty(keyValuePair))
+ {
+ continue;
+ }
+
+ string[] split = keyValuePair.Split('=');
+ base.Add(split[0], split.Length == 2 ? (urlDecode ? HttpUtility.UrlDecode(split[1]) : split[1]) : "");
+ }
+ return this;
+ }
+
+ ///
+ /// returns a QueryString object based on the current querystring of the request
+ ///
+ /// the QueryString object
+ public QueryString FromCurrent()
+ {
+ if (HttpContext.Current != null)
+ {
+ return FillFromString(HttpContext.Current.Request.QueryString.ToString(), true);
+ }
+ base.Clear();
+ return this;
+ }
+
+ ///
+ /// add a name value pair to the collection
+ ///
+ /// the name
+ /// the value associated to the name
+ /// the QueryString object
+ public new QueryString Add(string name, string value)
+ {
+ return Add(name, value, false);
+ }
+
+ ///
+ /// adds a name value pair to the collection
+ ///
+ /// the name
+ /// the value associated to the name
+ /// true if the name is unique within the querystring. This allows us to override existing values
+ /// the QueryString object
+ public QueryString Add(string name, string value, bool isUnique)
+ {
+ string existingValue = base[name];
+ if (string.IsNullOrEmpty(existingValue))
+ {
+ base.Add(name, HttpUtility.UrlEncode(value));
+ }
+ else if (isUnique)
+ {
+ base[name] = HttpUtility.UrlEncode(value);
+ }
+ else
+ {
+ base[name] += "," + HttpUtility.UrlEncode(value);
+ }
+ return this;
+ }
+
+ ///
+ /// removes a name value pair from the querystring collection
+ ///
+ /// name of the querystring value to remove
+ /// the QueryString object
+ public new QueryString Remove(string name)
+ {
+ string existingValue = base[name];
+ if (!string.IsNullOrEmpty(existingValue))
+ {
+ base.Remove(name);
+ }
+ return this;
+ }
+
+ ///
+ /// clears the collection
+ ///
+ /// the QueryString object
+ public QueryString Reset()
+ {
+ base.Clear();
+ return this;
+ }
+
+ ///
+ /// overrides the default
+ ///
+ ///
+ /// the associated decoded value for the specified name
+ public new string this[string name]
+ {
+ get { return HttpUtility.UrlDecode(base[name]); }
+ }
+
+ ///
+ /// overrides the default indexer
+ ///
+ ///
+ /// the associated decoded value for the specified index
+ public new string this[int index]
+ {
+ get { return HttpUtility.UrlDecode(base[index]); }
+ }
+
+ ///
+ /// checks if a name already exists within the query string collection
+ ///
+ /// the name to check
+ /// a boolean if the name exists
+ public bool Contains(string name)
+ {
+ string existingValue = base[name];
+ return !string.IsNullOrEmpty(existingValue);
+ }
+
+ ///
+ /// outputs the querystring object to a string
+ ///
+ /// the encoded querystring as it would appear in a browser
+ public override string ToString()
+ {
+ var builder = new StringBuilder();
+ for (var i = 0; i < base.Keys.Count; i++)
+ {
+ if (!string.IsNullOrEmpty(base.Keys[i]))
+ {
+ foreach (string val in base[base.Keys[i]].EmptyNull().Split(','))
+ {
+ builder.Append((builder.Length == 0) ? "?" : "&").Append(
+ HttpUtility.UrlEncode(base.Keys[i])).Append("=").Append(val);
+ }
+ }
+ }
+ return builder.ToString();
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Collections/QuerystringBuilder.cs b/src/Libraries/SmartStore.Core/Collections/QuerystringBuilder.cs
deleted file mode 100644
index 739f54d67e..0000000000
--- a/src/Libraries/SmartStore.Core/Collections/QuerystringBuilder.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-using System.Text;
-using System.Web;
-using System.Collections.Specialized;
-
-namespace SmartStore.Collections
-{
- ///
- /// http://weblogs.asp.net/bradvincent/archive/2008/10/27/helper-class-querystring-builder-chainable.aspx
- ///
- public class QueryString : NameValueCollection
- {
- public QueryString()
- {
- }
-
- public QueryString(string queryString)
- {
- FillFromString(queryString);
- }
-
- public static QueryString Current
- {
- get { return new QueryString().FromCurrent(); }
- }
-
- ///
- /// extracts a querystring from a full URL
- ///
- /// the string to extract the querystring from
- /// a string representing only the querystring
- public string ExtractQuerystring(string s)
- {
- if (!string.IsNullOrEmpty(s))
- {
- if (s.Contains("?"))
- {
- return s.Substring(s.IndexOf("?") + 1);
- }
- }
- return s;
- }
-
- ///
- /// returns a querystring object based on a string
- ///
- /// the string to parse
- /// the QueryString object
- public QueryString FillFromString(string s)
- {
- base.Clear();
- if (string.IsNullOrEmpty(s))
- {
- return this;
- }
- foreach (string keyValuePair in ExtractQuerystring(s).Split('&'))
- {
- if (string.IsNullOrEmpty(keyValuePair))
- {
- continue;
- }
- string[] split = keyValuePair.Split('=');
- base.Add(split[0],
- split.Length == 2 ? split[1] : "");
- }
- return this;
- }
-
- ///
- /// returns a QueryString object based on the current querystring of the request
- ///
- /// the QueryString object
- public QueryString FromCurrent()
- {
- if (HttpContext.Current != null)
- {
- return FillFromString(HttpContext.Current.Request.QueryString.ToString());
- }
- base.Clear();
- return this;
- }
-
- ///
- /// add a name value pair to the collection
- ///
- /// the name
- /// the value associated to the name
- /// the QueryString object
- public new QueryString Add(string name, string value)
- {
- return Add(name, value, false);
- }
-
- ///
- /// adds a name value pair to the collection
- ///
- /// the name
- /// the value associated to the name
- /// true if the name is unique within the querystring. This allows us to override existing values
- /// the QueryString object
- public QueryString Add(string name, string value, bool isUnique)
- {
- string existingValue = base[name];
- if (string.IsNullOrEmpty(existingValue))
- {
- base.Add(name, HttpUtility.UrlEncode(value));
- }
- else if (isUnique)
- {
- base[name] = HttpUtility.UrlEncode(value);
- }
- else
- {
- base[name] += "," + HttpUtility.UrlEncode(value);
- }
- return this;
- }
-
- ///
- /// removes a name value pair from the querystring collection
- ///
- /// name of the querystring value to remove
- /// the QueryString object
- public new QueryString Remove(string name)
- {
- string existingValue = base[name];
- if (!string.IsNullOrEmpty(existingValue))
- {
- base.Remove(name);
- }
- return this;
- }
-
- ///
- /// clears the collection
- ///
- /// the QueryString object
- public QueryString Reset()
- {
- base.Clear();
- return this;
- }
-
- ///
- /// overrides the default
- ///
- ///
- /// the associated decoded value for the specified name
- public new string this[string name]
- {
- get { return HttpUtility.UrlDecode(base[name]); }
- }
-
- ///
- /// overrides the default indexer
- ///
- ///
- /// the associated decoded value for the specified index
- public new string this[int index]
- {
- get { return HttpUtility.UrlDecode(base[index]); }
- }
-
- ///
- /// checks if a name already exists within the query string collection
- ///
- /// the name to check
- /// a boolean if the name exists
- public bool Contains(string name)
- {
- string existingValue = base[name];
- return !string.IsNullOrEmpty(existingValue);
- }
-
- ///
- /// outputs the querystring object to a string
- ///
- /// the encoded querystring as it would appear in a browser
- public override string ToString()
- {
- var builder = new StringBuilder();
- for (var i = 0; i < base.Keys.Count; i++)
- {
- if (!string.IsNullOrEmpty(base.Keys[i]))
- {
- foreach (string val in base[base.Keys[i]].Split(','))
- {
- builder.Append((builder.Length == 0) ? "?" : "&").Append(
- HttpUtility.UrlEncode(base.Keys[i])).Append("=").Append(val);
- }
- }
- }
- return builder.ToString();
- }
- }
-}
diff --git a/src/Libraries/SmartStore.Core/Collections/ThreadSafeList.cs b/src/Libraries/SmartStore.Core/Collections/ThreadSafeList.cs
deleted file mode 100644
index 04737fad68..0000000000
--- a/src/Libraries/SmartStore.Core/Collections/ThreadSafeList.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-
-using SmartStore.Utilities.Threading;
-
-namespace SmartStore.Collections
-{
-
- [Serializable]
- public class ThreadSafeList : IList, IList
- {
- #region Fields
-
- private List _list;
-
- [NonSerialized]
- private readonly ReaderWriterLockSlim _rwLock;
-
- #endregion
-
- #region Ctor
-
- public ThreadSafeList()
- : this(new ReaderWriterLockSlim())
- {
- }
-
- public ThreadSafeList(ReaderWriterLockSlim rwLock)
- {
- _list = new List();
- _rwLock = rwLock ?? new ReaderWriterLockSlim();
- }
-
- public ThreadSafeList(int capacity)
- : this(capacity, null)
- {
- }
-
- public ThreadSafeList(int capacity, ReaderWriterLockSlim rwLock)
- {
- _list = new List(capacity);
- _rwLock = rwLock ?? new ReaderWriterLockSlim();
- }
-
- public ThreadSafeList(IEnumerable collection)
- : this(collection, null)
- {
- }
-
- public ThreadSafeList(IEnumerable collection, ReaderWriterLockSlim rwLock)
- {
- _list = new List(collection);
- _rwLock = rwLock ?? new ReaderWriterLockSlim();
- }
-
- #endregion
-
- #region Properties
-
- public int Count
- {
- get
- {
- using (_rwLock.GetReadLock())
- {
- return _list.Count;
- }
- }
- }
-
- public bool IsReadOnly
- {
- get
- {
- return false;
- }
- }
-
- public T this[int index]
- {
- get
- {
- using (_rwLock.GetReadLock())
- {
- return _list[index];
- }
- }
- set
- {
- using (_rwLock.GetWriteLock())
- {
- _list[index] = value;
- }
- }
- }
-
- public ReaderWriterLockSlim Lock
- {
- get
- {
- return _rwLock;
- }
- }
-
- #endregion
-
- #region Methods
-
- public void Add(T item)
- {
- using (_rwLock.GetWriteLock())
- {
- _list.Add(item);
- }
- }
-
- public void Clear()
- {
- using (_rwLock.GetWriteLock())
- {
- _list.Clear();
- }
- }
-
- public bool Contains(T item)
- {
- using (_rwLock.GetReadLock())
- {
- return _list.Contains(item);
- }
- }
-
- public void CopyTo(T[] array, int arrayIndex)
- {
- using (_rwLock.GetWriteLock())
- {
- _list.CopyTo(array, arrayIndex);
- }
- }
-
- public IEnumerator GetEnumerator()
- {
- using (_rwLock.GetReadLock())
- {
- return _list.AsReadOnly().GetEnumerator();
- }
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return this.GetEnumerator();
- }
-
- public int IndexOf(T item)
- {
- using (_rwLock.GetReadLock())
- {
- return _list.IndexOf(item);
- }
- }
-
- public void Insert(int index, T item)
- {
- using (_rwLock.GetWriteLock())
- {
- _list.Insert(index, item);
- }
- }
-
- public bool Remove(T item)
- {
- using (_rwLock.GetWriteLock())
- {
- return _list.Remove(item);
- }
- }
-
- public void RemoveAt(int index)
- {
- using (_rwLock.GetWriteLock())
- {
- _list.RemoveAt(index);
- }
- }
-
- private static void VerifyValueType(object value)
- {
- if (!IsCompatibleObject(value))
- {
- throw Error.Argument("value", "Argument '{0}' is of wrong type. It must be '{1}'.", "value", typeof(T));
- }
- }
-
- private static bool IsCompatibleObject(object value)
- {
- if (!(value is T) && ((value != null) || typeof(T).IsValueType))
- {
- return false;
- }
- return true;
- }
-
- #endregion
-
- #region IList Members
-
- int IList.Add(object value)
- {
- VerifyValueType(value);
- this.Add((T)value);
- return this.Count - 1;
- }
-
- bool IList.Contains(object value)
- {
- return (IsCompatibleObject(value) && this.Contains((T)value));
- }
-
- int IList.IndexOf(object value)
- {
- if (IsCompatibleObject(value))
- {
- return this.IndexOf((T)value);
- }
- return -1;
- }
-
- void IList.Insert(int index, object value)
- {
- VerifyValueType(value);
- this.Insert(index, (T)value);
- }
-
- bool IList.IsFixedSize
- {
- get { return false; }
- }
-
- void IList.Remove(object value)
- {
- if (IsCompatibleObject(value))
- {
- this.Remove((T)value);
- }
- }
-
- object IList.this[int index]
- {
- get
- {
- return this[index];
- }
- set
- {
- VerifyValueType(value);
- this[index] = (T)value;
- }
- }
-
- #endregion
-
- #region ICollection Members
-
- void ICollection.CopyTo(Array array, int index)
- {
- using (_rwLock.GetWriteLock())
- {
- ((ICollection)_list).CopyTo(array, index);
- }
- }
-
- bool ICollection.IsSynchronized
- {
- get { return true; }
- }
-
- public object SyncRoot
- {
- get
- {
- return ((ICollection)_list).SyncRoot;
- }
- }
-
- #endregion
- }
-
-}
diff --git a/src/Libraries/SmartStore.Core/Collections/TopologicalSorter.cs b/src/Libraries/SmartStore.Core/Collections/TopologicalSorter.cs
new file mode 100644
index 0000000000..5fc2b6818a
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Collections/TopologicalSorter.cs
@@ -0,0 +1,220 @@
+// CREDITS to Tawani Anyangwe: http://tawani.blogspot.de/2009/02/topological-sorting-and-cyclic.html
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SmartStore.Collections
+{
+
+ public interface ITopologicSortable
+ {
+ TKey Key { get; }
+ TKey[] DependsOn { get; }
+ }
+
+ public static class TopologicalSortExtensions
+ {
+
+ public static ITopologicSortable[] SortTopological(this ITopologicSortable[] items)
+ {
+ return SortTopological(items, null);
+ }
+
+ public static ITopologicSortable[] SortTopological(this ITopologicSortable[] items, IEqualityComparer comparer)
+ {
+ Guard.ArgumentNotNull(() => items);
+
+ var sortedIndexes = SortIndexesTopological(items, comparer);
+ var sortedList = new List>(sortedIndexes.Length);
+
+ for (var i = 0; i < sortedIndexes.Length; i++)
+ {
+ //sortedList[i] = items[sortedIndexes[i]];
+ sortedList.Add(items[sortedIndexes[i]]);
+ }
+
+ return sortedList.ToArray();
+ }
+
+ public static int[] SortIndexesTopological(this ITopologicSortable[] items)
+ {
+ return SortIndexesTopological(items, null);
+ }
+
+ public static int[] SortIndexesTopological(this ITopologicSortable[] items, IEqualityComparer comparer)
+ {
+ Guard.ArgumentNotNull(() => items);
+
+ if (items.Length == 0)
+ {
+ return new int[] { };
+ }
+
+ if (comparer == null)
+ {
+ comparer = EqualityComparer.Default;
+ }
+
+ var sorter = new TopologicalSorter(items.Length);
+ var indexes = new Dictionary(comparer);
+
+ // add vertices
+ for (int i = 0; i < items.Length; i++)
+ {
+ indexes[items[i].Key] = sorter.AddVertex(i);
+ }
+
+ // add edges
+ for (int i = 0; i < items.Length; i++)
+ {
+ if (items[i].DependsOn != null)
+ {
+ for (int j = 0; j < items[i].DependsOn.Length; j++)
+ {
+ if (indexes.ContainsKey(items[i].DependsOn[j]))
+ {
+ sorter.AddEdge(i, indexes[items[i].DependsOn[j]]);
+ }
+ }
+ }
+ }
+
+ int[] result = sorter.Sort().Reverse().ToArray();
+ return result;
+ }
+ }
+
+ public class CyclicDependencyException : Exception
+ {
+ public CyclicDependencyException()
+ : base("Cyclic dependency detected")
+ {
+ }
+
+ public CyclicDependencyException(string message)
+ : base(message)
+ {
+ }
+ }
+
+ internal class TopologicalSorter
+ {
+ #region Private Members
+
+ private readonly int[] _vertices; // list of vertices
+ private readonly int[,] _matrix; // adjacency matrix
+ private int _numVerts; // current number of vertices
+ private readonly int[] _sortedArray;
+
+ #endregion
+
+ #region Ctor
+
+ public TopologicalSorter(int size)
+ {
+ _vertices = new int[size];
+ _matrix = new int[size, size];
+ _numVerts = 0;
+ for (int i = 0; i < size; i++)
+ {
+ for (int j = 0; j < size; j++)
+ {
+ _matrix[i, j] = 0;
+ }
+ }
+ _sortedArray = new int[size]; // sorted vert labels
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public int AddVertex(int vertex)
+ {
+ _vertices[_numVerts++] = vertex;
+ return _numVerts - 1;
+ }
+
+ public void AddEdge(int start, int end)
+ {
+ _matrix[start, end] = 1;
+ }
+
+ public int[] Sort() // topological sort
+ {
+ while (_numVerts > 0) // while vertices remain,
+ {
+ // get a vertex with no successors, or -1
+ int currentVertex = NoSuccessors();
+ if (currentVertex == -1)
+ {
+ // must be a cycle
+ throw new CyclicDependencyException();
+ }
+
+ // insert vertex label in sorted array (start at end)
+ _sortedArray[_numVerts - 1] = _vertices[currentVertex];
+
+ DeleteVertex(currentVertex); // delete vertex
+ }
+
+ // vertices all gone; return sortedArray
+ return _sortedArray;
+ }
+
+ #endregion
+
+ #region Private Helper Methods
+
+ // returns vert with no successors (or -1 if no such verts)
+ private int NoSuccessors()
+ {
+ for (int row = 0; row < _numVerts; row++)
+ {
+ bool isEdge = false; // edge from row to column in adjMat
+ for (int col = 0; col < _numVerts; col++)
+ {
+ if (_matrix[row, col] > 0) // if edge to another,
+ {
+ isEdge = true;
+ break; // this vertex has a successor try another
+ }
+ }
+ if (!isEdge) // if no edges, has no successors
+ return row;
+ }
+ return -1; // no
+ }
+
+ private void DeleteVertex(int delVert)
+ {
+ // if not last vertex, delete from vertexList
+ if (delVert != _numVerts - 1)
+ {
+ for (int j = delVert; j < _numVerts - 1; j++)
+ _vertices[j] = _vertices[j + 1];
+
+ for (int row = delVert; row < _numVerts - 1; row++)
+ MoveRowUp(row, _numVerts);
+
+ for (int col = delVert; col < _numVerts - 1; col++)
+ MoveColLeft(col, _numVerts - 1);
+ }
+ _numVerts--; // one less vertex
+ }
+
+ private void MoveRowUp(int row, int length)
+ {
+ for (int col = 0; col < length; col++)
+ _matrix[row, col] = _matrix[row + 1, col];
+ }
+
+ private void MoveColLeft(int col, int length)
+ {
+ for (int row = 0; row < length; row++)
+ _matrix[row, col] = _matrix[row, col + 1];
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs b/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs
deleted file mode 100644
index 81c2ecb4f7..0000000000
--- a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs
+++ /dev/null
@@ -1,416 +0,0 @@
-#region License
-/*
- **************************************************************
- * Author: Rick Strahl
- * © West Wind Technologies, 2012
- * http://www.west-wind.com/
- *
- * Created: Feb 2, 2012
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- **************************************************************
-*/
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Dynamic;
-using System.Reflection;
-using Fasterflect;
-
-namespace SmartStore.ComponentModel
-{
- ///
- /// Class that provides extensible properties and methods to an
- /// existing object when cast to dynamic. This
- /// dynamic object stores 'extra' properties in a dictionary or
- /// checks the actual properties of the instance passed via
- /// constructor.
- ///
- /// This class can be subclassed to extend an existing type or
- /// you can pass in an instance to extend. Properties (both
- /// dynamic and strongly typed) can be accessed through an
- /// indexer.
- ///
- /// This type allows you three ways to access its properties:
- ///
- /// Directly: any explicitly declared properties are accessible
- /// Dynamic: dynamic cast allows access to dictionary and native properties/methods
- /// Dictionary: Any of the extended properties are accessible via IDictionary interface
- ///
- [Serializable]
- public class Expando : DynamicObject, IDynamicMetaObjectProvider
- {
- ///
- /// Instance of object passed in
- ///
- private object _instance;
-
- ///
- /// Cached type of the instance
- ///
- private Type _instanceType;
-
- private IList _instancePropertyInfo;
-
- ///
- /// String Dictionary that contains the extra dynamic values
- /// stored on this object/instance
- ///
- /// Using PropertyBag to support XML Serialization of the dictionary
- public PropertyBag Properties = new PropertyBag();
-
- ///
- /// This constructor just works off the internal dictionary and any
- /// public properties of this object.
- ///
- /// Note you can subclass Expando.
- ///
- public Expando()
- {
- Initialize(this);
- }
-
- ///
- /// Allows passing in an existing instance variable to 'extend'.
- ///
- ///
- /// You can pass in null here if you don't want to
- /// check native properties and only check the Dictionary!
- ///
- ///
- public Expando(object instance)
- {
- Initialize(instance);
- }
-
-
- protected virtual void Initialize(object instance)
- {
- _instance = instance;
- if (instance != null)
- _instanceType = instance.GetType();
- }
-
- IList InstancePropertyInfo
- {
- get
- {
- if (_instancePropertyInfo == null && _instance != null)
- _instancePropertyInfo = _instance.GetType().Properties(Flags.InstancePublicDeclaredOnly);
- return _instancePropertyInfo;
- }
- }
-
- public override IEnumerable GetDynamicMemberNames()
- {
- foreach (var prop in this.GetProperties(false))
- yield return prop.Key;
- }
-
-
- ///
- /// Try to retrieve a member by name first from instance properties
- /// followed by the collection entries.
- ///
- ///
- ///
- ///
- public override bool TryGetMember(GetMemberBinder binder, out object result)
- {
- result = null;
-
- // first check the Properties collection for member
- if (Properties.Keys.Contains(binder.Name))
- {
- result = Properties[binder.Name];
- return true;
- }
-
-
- // Next check for Public properties via Reflection
- if (_instance != null)
- {
- try
- {
- return GetProperty(_instance, binder.Name, out result);
- }
- catch { }
- }
-
- // failed to retrieve a property
- result = null;
- return false;
- }
-
-
- ///
- /// Property setter implementation tries to retrieve value from instance
- /// first then into this object
- ///
- ///
- ///
- ///
- public override bool TrySetMember(SetMemberBinder binder, object value)
- {
-
- // first check to see if there's a native property to set
- if (_instance != null)
- {
- try
- {
- bool result = SetProperty(_instance, binder.Name, value);
- if (result)
- return true;
- }
- catch { }
- }
-
- // no match - set or add to dictionary
- Properties[binder.Name] = value;
- return true;
- }
-
- ///
- /// Dynamic invocation method. Currently allows only for Reflection based
- /// operation (no ability to add methods dynamically).
- ///
- ///
- ///
- ///
- ///
- public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
- {
- if (_instance != null)
- {
- try
- {
- // check instance passed in for methods to invoke
- if (InvokeMethod(_instance, binder.Name, args, out result))
- return true;
- }
- catch { }
- }
-
- result = null;
- return false;
- }
-
-
- ///
- /// Reflection Helper method to retrieve a property
- ///
- ///
- ///
- ///
- ///
- protected bool GetProperty(object instance, string name, out object result)
- {
- if (instance == null)
- instance = this;
-
- var pi = _instanceType.Property(name, Flags.InstancePublic);
- if (pi != null)
- {
- result = instance.GetPropertyValue(pi.Name);
- return true;
- }
-
- result = null;
- return false;
- }
-
- ///
- /// Reflection helper method to set a property value
- ///
- ///
- ///
- ///
- ///
- protected bool SetProperty(object instance, string name, object value)
- {
- if (instance == null)
- instance = this;
-
- var pi = _instanceType.Property(name, Flags.InstancePublic);
- if (pi != null)
- {
- instance.SetPropertyValue(pi.Name, value);
- return true;
- }
- return false;
- }
-
- ///
- /// Reflection helper method to invoke a method
- ///
- ///
- ///
- ///
- ///
- ///
- protected bool InvokeMethod(object instance, string name, object[] args, out object result)
- {
- if (instance == null)
- instance = this;
-
- // Look at the instanceType
- var mi = _instanceType.Method(name, Flags.InstancePublic);
-
- if (mi != null)
- {
- result = _instance.CallMethod(mi.Name, args);
- return true;
- }
-
- result = null;
- return false;
- }
-
-
- ///
- /// Convenience method that provides a string Indexer
- /// to the Properties collection AND the strongly typed
- /// properties of the object by name.
- ///
- /// // dynamic
- /// exp["Address"] = "112 nowhere lane";
- /// // strong
- /// var name = exp["StronglyTypedProperty"] as string;
- ///
- ///
- /// The getter checks the Properties dictionary first
- /// then looks in PropertyInfo for properties.
- /// The setter checks the instance properties before
- /// checking the Properties dictionary.
- ///
- ///
- ///
- ///
- public object this[string key]
- {
- get
- {
- try
- {
- // try to get from properties collection first
- return Properties[key];
- }
- catch (KeyNotFoundException)
- {
- // try reflection on instanceType
- object result = null;
- if (GetProperty(_instance, key, out result))
- return result;
-
- // no doesn't exist
- throw;
- }
- }
- set
- {
- if (Properties.ContainsKey(key))
- {
- Properties[key] = value;
- return;
- }
-
- // check instance for existance of type first
- var pi = _instanceType.Property(key, Flags.Public);
- if (pi != null)
- SetProperty(_instance, key, value);
- else
- Properties[key] = value;
- }
- }
-
-
- ///
- /// Returns and the properties of
- ///
- ///
- ///
- public IEnumerable> GetProperties(bool includeInstanceProperties = false)
- {
- if (includeInstanceProperties && _instance != null)
- {
- foreach (var prop in this.InstancePropertyInfo)
- yield return new KeyValuePair(prop.Name, prop.GetValue(_instance, null));
- }
-
- foreach (var key in this.Properties.Keys)
- yield return new KeyValuePair(key, this.Properties[key]);
-
- }
-
- ///
- /// Checks whether a property exists in the Property collection
- /// or as a property on the instance
- ///
- ///
- ///
- public bool Contains(KeyValuePair item, bool includeInstanceProperties = false)
- {
- return this.Contains(item.Key, includeInstanceProperties);
- }
-
- ///
- /// Checks whether a property exists in the Property collection
- /// or as a property on the instance
- ///
- ///
- ///
- ///
- public bool Contains(string propertyName, bool includeInstanceProperties = false)
- {
- bool res = Properties.ContainsKey(propertyName);
- if (res)
- return true;
-
- if (includeInstanceProperties && _instance != null)
- {
- foreach (var prop in this.InstancePropertyInfo)
- {
- if (prop.Name == propertyName)
- return true;
- }
- }
-
- return false;
- }
-
- public bool TryGetValue(string propertyName, out object value)
- {
- value = null;
-
- if (this.Contains(propertyName, true))
- {
- value = this[propertyName];
- return true;
- }
-
- return false;
- }
-
- }
-}
\ No newline at end of file
diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs
new file mode 100644
index 0000000000..79a4362f30
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Linq.Expressions;
+using System.Collections.Concurrent;
+
+namespace SmartStore.ComponentModel
+{
+ public class FastActivator
+ {
+ private static readonly ConcurrentDictionary _activatorsCache = new ConcurrentDictionary();
+
+ public FastActivator(ConstructorInfo constructorInfo)
+ {
+ Guard.ArgumentNotNull(() => constructorInfo);
+
+ Constructor = constructorInfo;
+ Invoker = MakeFastInvoker(constructorInfo);
+ ParameterTypes = constructorInfo.GetParameters().Select(p => p.ParameterType).ToArray();
+ }
+
+ ///
+ /// Gets the backing .
+ ///
+ public ConstructorInfo Constructor { get; private set; }
+
+ ///
+ /// Gets the parameter types from the backing
+ ///
+ public Type[] ParameterTypes { get; private set; }
+
+ ///
+ /// Gets the constructor invoker.
+ ///
+ public Func