recreate init

This commit is contained in:
l.gabrysiak 2024-08-21 16:09:17 +02:00
parent 1550c640d1
commit 9d939e2f2c
7229 changed files with 349321 additions and 339200 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,22 +1,60 @@
# create the build instance # create the build instance
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src WORKDIR /src
COPY ./src ./ COPY ./src ./
# restore solution
RUN dotnet restore NopCommerce.sln
WORKDIR /src/Presentation/Nop.Web WORKDIR /src/Presentation/Nop.Web
# build project # build project
RUN dotnet build Nop.Web.csproj -c Release RUN dotnet build Nop.Web.csproj -c Release
# build plugins # build plugins
WORKDIR /src/Plugins WORKDIR /src/Plugins/Nop.Plugin.DiscountRules.CustomerRoles
RUN set -eux; \ RUN dotnet build Nop.Plugin.DiscountRules.CustomerRoles.csproj -c Release
for dir in *; do \ WORKDIR /src/Plugins/Nop.Plugin.ExchangeRate.EcbExchange
if [ -d "$dir" ]; then \ RUN dotnet build Nop.Plugin.ExchangeRate.EcbExchange.csproj -c Release
dotnet build "$dir/$dir.csproj" -c Release; \ WORKDIR /src/Plugins/Nop.Plugin.ExternalAuth.Facebook
fi; \ RUN dotnet build Nop.Plugin.ExternalAuth.Facebook.csproj -c Release
done WORKDIR /src/Plugins/Nop.Plugin.Misc.Sendinblue
RUN dotnet build Nop.Plugin.Misc.Sendinblue.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Misc.WebApi.Frontend
RUN dotnet build Nop.Plugin.Misc.WebApi.Frontend.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator
RUN dotnet build Nop.Plugin.MultiFactorAuth.GoogleAuthenticator.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Payments.CheckMoneyOrder
RUN dotnet build Nop.Plugin.Payments.CheckMoneyOrder.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Payments.Manual
RUN dotnet build Nop.Plugin.Payments.Manual.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Payments.PayPalCommerce
RUN dotnet build Nop.Plugin.Payments.PayPalCommerce.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Payments.PayPalStandard
RUN dotnet build Nop.Plugin.Payments.PayPalStandard.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Pickup.PickupInStore
RUN dotnet build Nop.Plugin.Pickup.PickupInStore.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Shipping.EasyPost
RUN dotnet build Nop.Plugin.Shipping.EasyPost.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Shipping.FixedByWeightByTotal
RUN dotnet build Nop.Plugin.Shipping.FixedByWeightByTotal.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Shipping.UPS
RUN dotnet build Nop.Plugin.Shipping.UPS.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Tax.Avalara
RUN dotnet build Nop.Plugin.Tax.Avalara.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Tax.FixedOrByCountryStateZip
RUN dotnet build Nop.Plugin.Tax.FixedOrByCountryStateZip.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Widgets.AccessiBe
RUN dotnet build Nop.Plugin.Widgets.AccessiBe.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Widgets.FacebookPixel
RUN dotnet build Nop.Plugin.Widgets.FacebookPixel.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Widgets.GoogleAnalytics
RUN dotnet build Nop.Plugin.Widgets.GoogleAnalytics.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Widgets.NivoSlider
RUN dotnet build Nop.Plugin.Widgets.NivoSlider.csproj -c Release
WORKDIR /src/Plugins/Nop.Plugin.Widgets.What3words
RUN dotnet build Nop.Plugin.Widgets.What3words.csproj -c Release
# publish project # publish project
WORKDIR /src/Presentation/Nop.Web WORKDIR /src/Presentation/Nop.Web
@ -24,33 +62,33 @@ RUN dotnet publish Nop.Web.csproj -c Release -o /app/published
WORKDIR /app/published WORKDIR /app/published
RUN mkdir logs bin RUN mkdir logs
RUN mkdir bin
RUN chmod 775 App_Data \ RUN chmod 775 App_Data/
App_Data/DataProtectionKeys \ RUN chmod 775 App_Data/DataProtectionKeys
bin \ RUN chmod 775 bin
logs \ RUN chmod 775 logs
Plugins \ RUN chmod 775 Plugins
wwwroot/bundles \ RUN chmod 775 wwwroot/bundles
wwwroot/db_backups \ RUN chmod 775 wwwroot/db_backups
wwwroot/files/exportimport \ RUN chmod 775 wwwroot/files/exportimport
wwwroot/icons \ RUN chmod 775 wwwroot/icons
wwwroot/images \ RUN chmod 775 wwwroot/images
wwwroot/images/thumbs \ RUN chmod 775 wwwroot/images/thumbs
wwwroot/images/uploaded \ RUN chmod 775 wwwroot/images/uploaded
wwwroot/sitemaps
# create the runtime instance # create the runtime instance
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS runtime
# add globalization support # add globalization support
RUN apk add --no-cache icu-libs icu-data-full RUN apk add --no-cache icu-libs
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
# installs required packages # installs required packages
RUN apk add tiff --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/main/ --allow-untrusted RUN apk add libgdiplus --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ --allow-untrusted
RUN apk add libgdiplus --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/community/ --allow-untrusted RUN apk add libc-dev --no-cache
RUN apk add libc-dev tzdata --no-cache RUN apk add tzdata --no-cache
# copy entrypoint script # copy entrypoint script
COPY ./entrypoint.sh /entrypoint.sh COPY ./entrypoint.sh /entrypoint.sh

43
Jenkinsfile vendored
View File

@ -1,43 +0,0 @@
pipeline {
agent any
environment {
DOCKER_IMAGE = 'docker.cloud.pokash.pl/szkolenia.riskoff.pl'
DOCKER_REGISTRY = 'docker.cloud.pokash.pl'
GIT_REPO = 'https://repo.pokash.pl/POKASH.PL/SzkoleniaRiskoff.git'
REGISTRY_CREDENTIALS_ID = '2753fc17-5ad1-4c78-b86a-a3e54c543adc' // ID poświadczeń do lokalnego rejestru
}
stages {
stage('Checkout') {
steps {
git url: "${GIT_REPO}", branch: 'main'
}
}
stage('Build Docker Image') {
steps {
script {
docker.build("${DOCKER_IMAGE}:latest")
}
}
}
stage('Push Docker Image') {
steps {
script {
// Logowanie do lokalnego rejestru
docker.withRegistry("http://${DOCKER_REGISTRY}", "${REGISTRY_CREDENTIALS_ID}") {
docker.image("${DOCKER_IMAGE}:latest").push('latest')
}
}
}
}
}
post {
always {
cleanWs() // Czyści workspace po zakończeniu builda
}
}
}

View File

@ -1,15 +1,11 @@
# nopCommerce Public License Version 4.0 ("NPL") nopCommerce Public License Version 3.0 ("NPL")
nopCommerce open-source edition is licensed under nopCommerce Public License. It's basically a GNU Affero General Public License version 3 (GNU AGPL v3.0) plus the "powered by nopCommerce" text requirement on every single page. The nopCommerce Public License Version 4.0 ("NPL") consists of the GNU AGPL v3.0 License with the Additional Terms below. The original GNU AGPL v3.0 License can be found at: http://opensource.org/licenses/GPL-3.0
**Additional nopCommerce terms:** nopCommerce open source edition is licensed under nopCommerce Public License. It's basically a GPLv3 License plus the "powered by nopCommerce" text requirement on every single page. The nopCommerce Public License Version 3.0 ("NPL") consists of the GPL3 License with the Additional Terms below. The original GPLv3 License can be found at: http://opensource.org/licenses/GPL-3.0
However, in addition to the other notice obligations, (1) all copies of the Program in Executable and Source Code form must, as a form of attribution of the original author, include on each user interface screen (i) the "powered by nopCommerce" text; and (2) all derivative works and copies of derivative works of the Covered Code in Executable and Source Code form must include on each user interface screen (i) the "powered by nopCommerce" text. In addition, the "powered by nopCommerce" text, as appropriate, must be visible to all users, must appear in each user interface screen, and must be in the same position. When users click on the "powered by nopCommerce" text it must direct them to https://www.nopcommerce.com. This obligation shall also apply to any copies or derivative works. Find more info at https://www.nopcommerce.com/nopcommerce-copyright-removal-key Additional nopCommerce terms:
License page: https://www.nopcommerce.com/license However, in addition to the other notice obligations, (1) all copies of the Program in Executable and Source Code form must, as a form of attribution of the original author, include on each user interface screen (i) the "powered by nopCommerce" text; and (2) all derivative works and copies of derivative works of the Covered Code in Executable and Source Code form must include on each user interface screen (i) the "powered by nopCommerce" text. In addition, the "powered by nopCommerce" text, as appropriate, must be visible to all users, must appear in each user interface screen, and must be in the same position. When users click on the "powered by nopCommerce" text it must direct them to https://www.nopCommerce.com. This obligation shall also apply to any copies or derivative works. Find more info at https://www.nopcommerce.com/p/1/nopcommerce-copyright-removal-key.aspx
# Commercial License
Independent Software Vendors that want the benefits of embedding nopCommerce software in their commercial applications but do not want to be subject to the nopCommerce Public License ("NPL") and do not want to release the source code for their proprietary applications must purchase a commercial license from the nopCommerce team. Purchasing a commercial license means that the nopCommerce Public License ("NPL") does not apply, and a commercial license includes the assurances that distributors typically find in commercial distribution agreements. If use of nopCommerce under the NPL does not satisfy your organization's legal department you should also enter into a commercial license agreement with the nopCommerce team. License page: https://www.nopcommerce.com/licensev3.aspx
Feel free to contact us for more details - https://www.nopcommerce.com/contact-us

View File

@ -1,23 +1,29 @@
nopCommerce: free and open-source eCommerce solution nopCommerce: free and open-source eCommerce solution[![Build Status](https://travis-ci.com/nopSolutions/nopCommerce.svg?branch=develop)](https://travis-ci.com/nopSolutions/nopCommerce)
=========== ===========
[nopCommerce](https://www.nopcommerce.com/?utm_source=github&utm_medium=content&utm_campaign=homepage) is the best open-source eCommerce platform. nopCommerce is free, and it is the most popular ASP.NET Core shopping cart. [nopCommerce](https://www.nopcommerce.com/?utm_source=github&utm_medium=content&utm_campaign=homepage) is the best open-source eCommerce shopping cart solution. nopCommerce is free, and it is the most popular ASP.NET eCommerce platform.
![nopCommerce demo](https://www.nopcommerce.com/images/github/responsive_devices_codeplex.png#v1) ![nopCommerce demo](https://www.nopcommerce.com/images/github/responsive_devices_codeplex.png#v1)
### Key features ### The product is being developed and supported by the professional team since 2008.
* The product is being developed and supported by the professional team since 2008. nopCommerce has been downloaded more than 3,000,000 times.
* nopCommerce has been downloaded more than 3,000,000 times.
* The active developer community has more than 250,000 members. The active developer community has more than 250,000 members.
* nopCommerce runs on .NET 8 with an MS SQL 2012 (or higher) backend database.
* nopCommerce is cross-platform, and you can run it on Windows, Linux, or Mac. nopCommerce runs on ASP.NET Core 5 with an MS SQL 2012 (or higher) backend database.
* nopCommerce supports Docker out of the box, so you can easily run nopCommerce on a Linux machine.
* nopCommerce supports PostgreSQL and MySQL databases. nopCommerce is cross-platform, and you can run it on Windows, Linux, or Mac.
* nopCommerce fully supports web farms. You can read more about it [here](https://docs.nopcommerce.com/en/developer/tutorials/web-farms.html?utm_source=github&utm_medium=referral&utm_campaign=documentation&utm_content=text).
* All methods in nopCommerce are async. nopCommerce supports Docker and MySQL out of the box, so you can easily run nopCommerce on a Linux machine.
* nopCommerce supports multi-factor authentication out of the box.
* Start our [online course for developers](https://nopcommerce.com/training?utm_source=github&utm_medium=referral&utm_campaign=course&utm_content=text) and get the practical and technical skills you need to run and customize nopCommerce websites. nopCommerce supports PostgreSQL database.
nopCommerce fully supports web farms. You can read more about it [here](https://docs.nopcommerce.com/en/developer/tutorials/web-farms.html?utm_source=github&utm_medium=referral&utm_campaign=documentation&utm_content=text).
All methods in nopCommerce are async.
nopCommerce supports multi-factor authentication out of the box.
![Logo](https://www.nopcommerce.com/images/github/logos.png#v2) ![Logo](https://www.nopcommerce.com/images/github/logos.png#v2)
@ -25,7 +31,7 @@ nopCommerce architecture follows well-known software patterns and the best secur
Using the latest Microsoft technologies, nopCommerce provides high performance, stability, and security. nopCommerce is also fully compatible with Azure and web farms. Using the latest Microsoft technologies, nopCommerce provides high performance, stability, and security. nopCommerce is also fully compatible with Azure and web farms.
Our clear and detailed [documentation](https://docs.nopcommerce.com/developer/index.html?utm_source=github&utm_medium=referral&utm_campaign=documentation&utm_content=text) and [online course](https://nopcommerce.com/training?utm_source=github&utm_medium=referral&utm_campaign=course&utm_content=text) for developers will help you start with nopCommerce easily. Clear and detailed [documentation for developers](https://docs.nopcommerce.com/developer/index.html?utm_source=github&utm_medium=referral&utm_campaign=documentation&utm_content=text) will help you start with nopCommerce easily.
### The advantages of working with nopCommerce ### ### The advantages of working with nopCommerce ###
@ -34,8 +40,6 @@ nopCommerce offers powerful [out-of-the-box features](https://www.nopcommerce.co
nopCommerce is integrated with all the popular third-party services. You can find thousands of integrations on nopCommerce [Marketplace](https://www.nopcommerce.com/marketplace?utm_source=github&utm_medium=referral&utm_campaign=marketplace&utm_content=text). nopCommerce is integrated with all the popular third-party services. You can find thousands of integrations on nopCommerce [Marketplace](https://www.nopcommerce.com/marketplace?utm_source=github&utm_medium=referral&utm_campaign=marketplace&utm_content=text).
The [Web API plugin](https://www.nopcommerce.com/web-api?utm_source=github&utm_medium=referral&utm_campaign=WebAPI&utm_content=text) by the nopCommerce team lets you build integrations with third-party services or mobile applications using REST. The Web API plugin is available with source code and covers all methods of nopCommerce: backend and frontend. You can read more about it [here](https://www.nopcommerce.com/web-api?utm_source=github&utm_medium=referral&utm_campaign=WebAPI&utm_content=text).
Friendly members of the [nopCommerce community](https://www.nopcommerce.com/boards?utm_source=github&utm_medium=referral&utm_campaign=forum&utm_content=text) will always help with advice and share their experiences. nopCommerce core development team provides [professional support](https://www.nopcommerce.com/nopcommerce-premium-support-services?utm_source=github&utm_medium=referral&utm_campaign=premium_support&utm_content=text) within 24 hours. Friendly members of the [nopCommerce community](https://www.nopcommerce.com/boards?utm_source=github&utm_medium=referral&utm_campaign=forum&utm_content=text) will always help with advice and share their experiences. nopCommerce core development team provides [professional support](https://www.nopcommerce.com/nopcommerce-premium-support-services?utm_source=github&utm_medium=referral&utm_campaign=premium_support&utm_content=text) within 24 hours.
@ -45,7 +49,7 @@ Evaluate the functionality and convenience of nopCommerce as a customer and stor
Front End | Admin area Front End | Admin area
----|------ ----|------
[![ScreenShot](https://www.nopcommerce.com/images/github/public-demo.png#v1)](https://demo.nopcommerce.com?utm_source=github&utm_medium=referral&utm_campaign=demo_store&utm_content=button) | [![ScreenShot](https://www.nopcommerce.com/images/github/admin-demo.png#v1)](https://admin-demo.nopcommerce.com/admin?utm_source=github&utm_medium=referral&utm_campaign=demo_store&utm_content=button) [![ScreenShot](https://www.nopcommerce.com/images/github/public-demo.png#v1)](https://frontend.nopcommerce.com?utm_source=github&utm_medium=referral&utm_campaign=demo_store&utm_content=button) | [![ScreenShot](https://www.nopcommerce.com/images/github/admin-demo.png#v1)](https://admin-demo.nopcommerce.com/admin?utm_source=github&utm_medium=referral&utm_campaign=demo_store&utm_content=button)
### nopCommerce resources ### ### nopCommerce resources ###
@ -54,9 +58,7 @@ nopCommerce official site: [https://www.nopcommerce.com](https://www.nopcommerce
* [Demo store](https://www.nopcommerce.com/demo?utm_source=github&utm_medium=referral&utm_campaign=demo_store&utm_content=links) * [Demo store](https://www.nopcommerce.com/demo?utm_source=github&utm_medium=referral&utm_campaign=demo_store&utm_content=links)
* [Download nopCommerce](https://www.nopcommerce.com/download-nopcommerce?utm_source=github&utm_medium=referral&utm_campaign=download_nop&utm_content=links) * [Download nopCommerce](https://www.nopcommerce.com/download-nopcommerce?utm_source=github&utm_medium=referral&utm_campaign=download_nop&utm_content=links)
* [Online course for developers](https://nopcommerce.com/training?utm_source=github&utm_medium=referral&utm_campaign=course&utm_content=links)
* [Feature list](https://www.nopcommerce.com/features?utm_source=github&utm_medium=referral&utm_campaign=features&utm_content=links) * [Feature list](https://www.nopcommerce.com/features?utm_source=github&utm_medium=referral&utm_campaign=features&utm_content=links)
* [Web API plugin](https://www.nopcommerce.com/web-api?utm_source=github&utm_medium=referral&utm_campaign=WebAPI&utm_content=links)
* [nopCommerce documentation](https://docs.nopcommerce.com?utm_source=github&utm_medium=referral&utm_campaign=documentation&utm_content=links) * [nopCommerce documentation](https://docs.nopcommerce.com?utm_source=github&utm_medium=referral&utm_campaign=documentation&utm_content=links)
* [Community forums](https://www.nopcommerce.com/boards?utm_source=github&utm_medium=referral&utm_campaign=forum&utm_content=links) * [Community forums](https://www.nopcommerce.com/boards?utm_source=github&utm_medium=referral&utm_campaign=forum&utm_content=links)
* [Premium support services](https://www.nopcommerce.com/nopcommerce-premium-support-services?utm_source=github&utm_medium=referral&utm_campaign=premium_support&utm_content=links) * [Premium support services](https://www.nopcommerce.com/nopcommerce-premium-support-services?utm_source=github&utm_medium=referral&utm_campaign=premium_support&utm_content=links)
@ -77,4 +79,4 @@ Create a new graphical theme or develop a new plugin or integration and sell it
### Contribute ### ### Contribute ###
As a free and open-source project, we are very grateful to everyone who helps us to develop nopCommerce. Please find more details about the options and bonuses for contributors at [contribute page](https://www.nopcommerce.com/contribute?utm_source=github&utm_medium=referral&utm_campaign=contribute&utm_content=text). As a free and open-source project, we are very grateful to everyone who helps us to develop nopCommerce. Please find more details about the options and bonuses for contributors at [сontribute page](https://www.nopcommerce.com/contribute?utm_source=github&utm_medium=referral&utm_campaign=contribute&utm_content=text).

View File

@ -4,7 +4,7 @@ services:
build: . build: .
container_name: nopcommerce container_name: nopcommerce
ports: ports:
- "8010:80" - "80:80"
depends_on: depends_on:
- nopcommerce_database - nopcommerce_database
nopcommerce_database: nopcommerce_database:

View File

@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "8.0.204", "version": "6.0.101",
"rollForward": "latestFeature", "rollForward": "latestFeature",
"allowPrerelease": false "allowPrerelease": false
} }

BIN
src/.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Copyright>Copyright © Nop Solutions, Ltd</Copyright> <Copyright>Copyright © Nop Solutions, Ltd</Copyright>
<Company>Nop Solutions, Ltd</Company> <Company>Nop Solutions, Ltd</Company>
<Authors>Nop Solutions, Ltd</Authors> <Authors>Nop Solutions, Ltd</Authors>
<PackageLicenseUrl>https://www.nopcommerce.com/license</PackageLicenseUrl> <PackageLicenseUrl>http://www.nopcommerce.com/licensev3.aspx</PackageLicenseUrl>
<PackageProjectUrl>http://www.nopcommerce.com/</PackageProjectUrl> <PackageProjectUrl>http://www.nopcommerce.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl> <RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>

View File

@ -1,9 +1,9 @@
{ {
"runtimeOptions": { "runtimeOptions": {
"tfm": "net8.0", "tfm": "net6.0",
"framework": { "framework": {
"name": "Microsoft.NETCore.App", "name": "Microsoft.NETCore.App",
"version": "8.0.0" "version": "6.0.0"
} }
} }
} }

View File

@ -2,10 +2,9 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>

View File

@ -1,4 +1,8 @@
namespace ClearPluginAssemblies using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ClearPluginAssemblies
{ {
public class Program public class Program
{ {
@ -38,7 +42,7 @@
File.Delete(pdbfilePath); File.Delete(pdbfilePath);
} }
if (directoryInfo.GetFiles().Length == 0 && directoryInfo.GetDirectories().Length == 0 && !saveLocalesFolders) if (!directoryInfo.GetFiles().Any() && !directoryInfo.GetDirectories().Any() && !saveLocalesFolders)
directoryInfo.Delete(true); directoryInfo.Delete(true);
} }
} }
@ -55,7 +59,7 @@
var pluginPaths = string.Empty; var pluginPaths = string.Empty;
var saveLocalesFolders = true; var saveLocalesFolders = true;
var settings = args.FirstOrDefault(a => a.Contains('|')) ?? string.Empty; var settings = args.FirstOrDefault(a => a.Contains("|")) ?? string.Empty;
if(string.IsNullOrEmpty(settings)) if(string.IsNullOrEmpty(settings))
return; return;
@ -75,7 +79,7 @@
pluginPaths = value; pluginPaths = value;
break; break;
case "SaveLocalesFolders": case "SaveLocalesFolders":
_ = bool.TryParse(value, out saveLocalesFolders); bool.TryParse(value, out saveLocalesFolders);
break; break;
} }
} }
@ -84,13 +88,11 @@
return; return;
var di = new DirectoryInfo(outputPath); var di = new DirectoryInfo(outputPath);
var separator = Path.DirectorySeparatorChar;
var folderToIgnore = string.Concat(separator, "Plugins", separator);
var fileNames = di.GetFiles("*.dll", SearchOption.AllDirectories) var fileNames = di.GetFiles("*.dll", SearchOption.AllDirectories)
.Where(fi => !fi.FullName.Contains(folderToIgnore)) .Where(fi => !fi.FullName.Contains(@"\Plugins\"))
.Select(fi => fi.Name.Replace(fi.Extension, "")).ToList(); .Select(fi => fi.Name.Replace(fi.Extension, "")).ToList();
if (string.IsNullOrEmpty(pluginPaths) || fileNames.Count == 0) if (string.IsNullOrEmpty(pluginPaths) || !fileNames.Any())
{ {
return; return;
} }

View File

@ -1,5 +1,5 @@
namespace Nop.Core; namespace Nop.Core
{
/// <summary> /// <summary>
/// Represents the base class for entities /// Represents the base class for entities
/// </summary> /// </summary>
@ -10,3 +10,4 @@ public abstract partial class BaseEntity
/// </summary> /// </summary>
public int Id { get; set; } public int Id { get; set; }
} }
}

View File

@ -1,8 +1,11 @@
using Nop.Core.Configuration; using System;
using System.Collections.Generic;
using System.Linq;
using Nop.Core.Configuration;
using Nop.Core.Infrastructure; using Nop.Core.Infrastructure;
namespace Nop.Core.Caching; namespace Nop.Core.Caching
{
/// <summary> /// <summary>
/// Represents key for caching objects /// Represents key for caching objects
/// </summary> /// </summary>
@ -58,7 +61,7 @@ public partial class CacheKey
/// <summary> /// <summary>
/// Gets or sets prefixes for remove by prefix functionality /// Gets or sets prefixes for remove by prefix functionality
/// </summary> /// </summary>
public List<string> Prefixes { get; protected set; } = new(); public List<string> Prefixes { get; protected set; } = new List<string>();
/// <summary> /// <summary>
/// Gets or sets a cache time in minutes /// Gets or sets a cache time in minutes
@ -67,3 +70,4 @@ public partial class CacheKey
#endregion #endregion
} }
}

View File

@ -1,63 +0,0 @@
using Nop.Core.Infrastructure;
namespace Nop.Core.Caching;
/// <summary>
/// Cache key manager
/// </summary>
/// <remarks>
/// This class should be registered on IoC as singleton instance
/// </remarks>
public partial class CacheKeyManager : ICacheKeyManager
{
protected readonly IConcurrentCollection<byte> _keys;
public CacheKeyManager(IConcurrentCollection<byte> keys)
{
_keys = keys;
}
/// <summary>
/// Add the key
/// </summary>
/// <param name="key">The key to add</param>
public void AddKey(string key)
{
_keys.Add(key, default);
}
/// <summary>
/// Remove the key
/// </summary>
/// <param name="key">The key to remove</param>
public void RemoveKey(string key)
{
_keys.Remove(key);
}
/// <summary>
/// Remove all keys
/// </summary>
public void Clear()
{
_keys.Clear();
}
/// <summary>
/// Remove keys by prefix
/// </summary>
/// <param name="prefix">Prefix to delete keys</param>
/// <returns>The list of removed keys</returns>
public IEnumerable<string> RemoveByPrefix(string prefix)
{
if (!_keys.Prune(prefix, out var subtree) || subtree?.Keys == null)
return Enumerable.Empty<string>();
return subtree.Keys;
}
/// <summary>
/// The list of keys
/// </summary>
public IEnumerable<string> Keys => _keys.Keys;
}

View File

@ -1,14 +1,25 @@
using System.Globalization; using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text; using System.Text;
using Nop.Core.Configuration; using Nop.Core.Configuration;
namespace Nop.Core.Caching; namespace Nop.Core.Caching
{
/// <summary> /// <summary>
/// Represents the default cache key service implementation /// Represents the default cache key service implementation
/// </summary> /// </summary>
public abstract partial class CacheKeyService : ICacheKeyService public abstract partial class CacheKeyService
{ {
#region Constants
/// <summary>
/// Gets an algorithm used to create the hash value of identifiers need to cache
/// </summary>
private string HashAlgorithm => "SHA1";
#endregion
#region Fields #region Fields
protected readonly AppSettings _appSettings; protected readonly AppSettings _appSettings;
@ -102,14 +113,21 @@ public abstract partial class CacheKeyService : ICacheKeyService
return key; return key;
} }
#endregion
#region Properties
/// <summary> /// <summary>
/// Gets an algorithm used to create the hash value of identifiers need to cache /// Create a copy of cache key using the short cache time and fills it by passed parameters
/// </summary> /// </summary>
protected string HashAlgorithm => "SHA1"; /// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>Cache key</returns>
public virtual CacheKey PrepareKeyForShortTermCache(CacheKey cacheKey, params object[] cacheKeyParameters)
{
var key = cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters);
key.CacheTime = _appSettings.Get<CacheConfig>().ShortTermCacheTime;
return key;
}
#endregion #endregion
} }
}

View File

@ -1,29 +0,0 @@
namespace Nop.Core.Caching;
public static class CachingExtensions
{
/// <summary>
/// Get a cached item. If it's not in the cache yet, then load and cache it.
/// NOTE: this method is only kept for backwards compatibility: the async overload is preferred!
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="cacheManager">Cache manager</param>
/// <param name="key">Cache key</param>
/// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// <returns>The cached value associated with the specified key</returns>
public static T Get<T>(this IStaticCacheManager cacheManager, CacheKey key, Func<T> acquire)
{
return cacheManager.GetAsync(key, acquire).GetAwaiter().GetResult();
}
/// <summary>
/// Remove items by cache key prefix
/// </summary>
/// <param name="cacheManager">Cache manager</param>
/// <param name="prefix">Cache key prefix</param>
/// <param name="prefixParameters">Parameters to create cache key prefix</param>
public static void RemoveByPrefix(this IStaticCacheManager cacheManager, string prefix, params object[] prefixParameters)
{
cacheManager.RemoveByPrefixAsync(prefix, prefixParameters).Wait();
}
}

View File

@ -1,147 +0,0 @@
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
namespace Nop.Core.Caching;
public partial class DistributedCacheLocker : ILocker
{
#region Fields
protected static readonly string _running = JsonConvert.SerializeObject(TaskStatus.Running);
protected readonly IDistributedCache _distributedCache;
#endregion
#region Ctor
public DistributedCacheLocker(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}
#endregion
#region Methods
/// <summary>
/// Performs some asynchronous task with exclusive lock
/// </summary>
/// <param name="resource">The key we are locking on</param>
/// <param name="expirationTime">The time after which the lock will automatically be expired</param>
/// <param name="action">Asynchronous task to be performed with locking</param>
/// <returns>A task that resolves true if lock was acquired and action was performed; otherwise false</returns>
public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)
{
//ensure that lock is acquired
if (!string.IsNullOrEmpty(await _distributedCache.GetStringAsync(resource)))
return false;
try
{
await _distributedCache.SetStringAsync(resource, resource, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expirationTime
});
await action();
return true;
}
finally
{
//release lock even if action fails
await _distributedCache.RemoveAsync(resource);
}
}
/// <summary>
/// Starts a background task with "heartbeat": a status flag that will be periodically updated to signal to
/// others that the task is running and stop them from starting the same task.
/// </summary>
/// <param name="key">The key of the background task</param>
/// <param name="expirationTime">The time after which the heartbeat key will automatically be expired. Should be longer than <paramref name="heartbeatInterval"/></param>
/// <param name="heartbeatInterval">The interval at which to update the heartbeat, if required by the implementation</param>
/// <param name="action">Asynchronous background task to be performed</param>
/// <param name="cancellationTokenSource">A CancellationTokenSource for manually canceling the task</param>
/// <returns>A task that resolves true if lock was acquired and action was performed; otherwise false</returns>
public async Task RunWithHeartbeatAsync(string key, TimeSpan expirationTime, TimeSpan heartbeatInterval, Func<CancellationToken, Task> action, CancellationTokenSource cancellationTokenSource = default)
{
if (!string.IsNullOrEmpty(await _distributedCache.GetStringAsync(key)))
return;
var tokenSource = cancellationTokenSource ?? new CancellationTokenSource();
try
{
// run heartbeat early to minimize risk of multiple execution
await _distributedCache.SetStringAsync(
key,
_running,
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expirationTime },
token: tokenSource.Token);
await using var timer = new Timer(
callback: _ =>
{
try
{
tokenSource.Token.ThrowIfCancellationRequested();
var status = _distributedCache.GetString(key);
if (!string.IsNullOrEmpty(status) && JsonConvert.DeserializeObject<TaskStatus>(status) ==
TaskStatus.Canceled)
{
tokenSource.Cancel();
return;
}
_distributedCache.SetString(
key,
_running,
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expirationTime });
}
catch (OperationCanceledException) { }
},
state: null,
dueTime: 0,
period: (int)heartbeatInterval.TotalMilliseconds);
await action(tokenSource.Token);
}
catch (OperationCanceledException) { }
finally
{
await _distributedCache.RemoveAsync(key);
}
}
/// <summary>
/// Tries to cancel a background task by flagging it for cancellation on the next heartbeat.
/// </summary>
/// <param name="key">The task's key</param>
/// <param name="expirationTime">The time after which the task will be considered stopped due to system shutdown or other causes,
/// even if not explicitly canceled.</param>
/// <returns>A task that represents requesting cancellation of the task. Note that the completion of this task does not
/// necessarily imply that the task has been canceled, only that cancellation has been requested.</returns>
public async Task CancelTaskAsync(string key, TimeSpan expirationTime)
{
var status = await _distributedCache.GetStringAsync(key);
if (!string.IsNullOrEmpty(status) &&
JsonConvert.DeserializeObject<TaskStatus>(status) != TaskStatus.Canceled)
await _distributedCache.SetStringAsync(
key,
JsonConvert.SerializeObject(TaskStatus.Canceled),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expirationTime });
}
/// <summary>
/// Check if a background task is running.
/// </summary>
/// <param name="key">The task's key</param>
/// <returns>A task that resolves to true if the background task is running; otherwise false</returns>
public async Task<bool> IsTaskRunningAsync(string key)
{
return !string.IsNullOrEmpty(await _distributedCache.GetStringAsync(key));
}
#endregion
}

View File

@ -1,136 +1,129 @@
using System.Collections.Concurrent; using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json; using Newtonsoft.Json;
using Nito.AsyncEx;
using Nop.Core.ComponentModel;
using Nop.Core.Configuration; using Nop.Core.Configuration;
using Nop.Core.Infrastructure;
namespace Nop.Core.Caching;
namespace Nop.Core.Caching
{
/// <summary> /// <summary>
/// Represents a base distributed cache /// Represents a distributed cache
/// </summary> /// </summary>
public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheManager public partial class DistributedCacheManager: CacheKeyService, ILocker, IStaticCacheManager
{ {
#region Fields #region Fields
/// <summary> private readonly IDistributedCache _distributedCache;
/// Holds the keys known by this nopCommerce instance private readonly PerRequestCache _perRequestCache;
/// </summary> private static readonly List<string> _keys;
protected readonly ICacheKeyManager _localKeyManager; private static readonly AsyncLock _locker;
protected readonly IDistributedCache _distributedCache;
protected readonly IConcurrentCollection<object> _concurrentCollection;
/// <summary>
/// Holds ongoing acquisition tasks, used to avoid duplicating work
/// </summary>
protected readonly ConcurrentDictionary<string, Lazy<Task<object>>> _ongoing = new();
#endregion #endregion
#region Ctor #region Ctor
protected DistributedCacheManager(AppSettings appSettings, static DistributedCacheManager()
IDistributedCache distributedCache, {
ICacheKeyManager cacheKeyManager, _locker = new AsyncLock();
IConcurrentCollection<object> concurrentCollection) _keys = new List<string>();
: base(appSettings) }
public DistributedCacheManager(AppSettings appSettings, IDistributedCache distributedCache, IHttpContextAccessor httpContextAccessor) :base(appSettings)
{ {
_distributedCache = distributedCache; _distributedCache = distributedCache;
_localKeyManager = cacheKeyManager; _perRequestCache = new PerRequestCache(httpContextAccessor);
_concurrentCollection = concurrentCollection;
} }
#endregion #endregion
#region Utilities #region Utilities
/// <summary>
/// Clear all data on this instance
/// </summary>
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual void ClearInstanceData()
{
_concurrentCollection.Clear();
_localKeyManager.Clear();
}
/// <summary>
/// Remove items by cache key prefix
/// </summary>
/// <param name="prefix">Cache key prefix</param>
/// <param name="prefixParameters">Parameters to create cache key prefix</param>
/// <returns>The removed keys</returns>
protected virtual IEnumerable<string> RemoveByPrefixInstanceData(string prefix, params object[] prefixParameters)
{
var keyPrefix = PrepareKeyPrefix(prefix, prefixParameters);
_concurrentCollection.Prune(keyPrefix, out _);
return _localKeyManager.RemoveByPrefix(keyPrefix);
}
/// <summary> /// <summary>
/// Prepare cache entry options for the passed key /// Prepare cache entry options for the passed key
/// </summary> /// </summary>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
/// <returns>Cache entry options</returns> /// <returns>Cache entry options</returns>
protected virtual DistributedCacheEntryOptions PrepareEntryOptions(CacheKey key) private DistributedCacheEntryOptions PrepareEntryOptions(CacheKey key)
{ {
//set expiration time for the passed cache key //set expiration time for the passed cache key
return new DistributedCacheEntryOptions var options = new DistributedCacheEntryOptions
{ {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime) AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)
}; };
return options;
} }
/// <summary> /// <summary>
/// Add the specified key and object to the local cache /// Try to get the cached item
/// </summary>
/// <param name="key">Key of cached item</param>
/// <param name="value">Value for caching</param>
protected virtual void SetLocal(string key, object value)
{
_concurrentCollection.Add(key, value);
_localKeyManager.AddKey(key);
}
/// <summary>
/// Remove the value with the specified key from the cache
/// </summary>
/// <param name="key">Cache key</param>
protected virtual void RemoveLocal(string key)
{
_concurrentCollection.Remove(key);
_localKeyManager.RemoveKey(key);
}
/// <summary>
/// Try get a cached item. If it's not in the cache yet, then return default object
/// </summary> /// </summary>
/// <typeparam name="T">Type of cached item</typeparam> /// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
protected virtual async Task<(bool isSet, T item)> TryGetItemAsync<T>(string key) /// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the flag which indicate is the key exists in the cache, cached item or default value
/// </returns>
private async Task<(bool isSet, T item)> TryGetItemAsync<T>(CacheKey key)
{ {
var json = await _distributedCache.GetStringAsync(key); var json = await _distributedCache.GetStringAsync(key.Key);
return string.IsNullOrEmpty(json) if (string.IsNullOrEmpty(json))
? (false, default) return (false, default);
: (true, item: JsonConvert.DeserializeObject<T>(json));
var item = JsonConvert.DeserializeObject<T>(json);
_perRequestCache.Set(key.Key, item);
using var _ = await _locker.LockAsync();
_keys.Add(key.Key);
return (true, item);
} }
/// <summary> /// <summary>
/// Remove the value with the specified key from the cache /// Try to get the cached item
/// </summary> /// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
/// <param name="removeFromInstance">Remove from instance</param> /// <returns>Flag which indicate is the key exists in the cache, cached item or default value</returns>
protected virtual async Task RemoveAsync(string key, bool removeFromInstance = true) private (bool isSet, T item) TryGetItem<T>(CacheKey key)
{ {
_ongoing.TryRemove(key, out _); var json = _distributedCache.GetString(key.Key);
await _distributedCache.RemoveAsync(key);
if (!removeFromInstance) if (string.IsNullOrEmpty(json))
return (false, default);
var item = JsonConvert.DeserializeObject<T>(json);
_perRequestCache.Set(key.Key, item);
using var _ = _locker.Lock();
_keys.Add(key.Key);
return (true, item);
}
/// <summary>
/// Add the specified key and object to the cache
/// </summary>
/// <param name="key">Key of cached item</param>
/// <param name="data">Value for caching</param>
private void Set(CacheKey key, object data)
{
if ((key?.CacheTime ?? 0) <= 0 || data == null)
return; return;
RemoveLocal(key); _distributedCache.SetString(key.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));
_perRequestCache.Set(key.Key, data);
using var _ = _locker.Lock();
_keys.Add(key.Key);
} }
#endregion #endregion
@ -138,14 +131,11 @@ public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheMan
#region Methods #region Methods
/// <summary> /// <summary>
/// Remove the value with the specified key from the cache /// Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
/// <param name="cacheKey">Cache key</param> public void Dispose()
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>A task that represents the asynchronous operation</returns>
public async Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
{ {
await RemoveAsync(PrepareKey(cacheKey, cacheKeyParameters).Key);
} }
/// <summary> /// <summary>
@ -160,39 +150,26 @@ public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheMan
/// </returns> /// </returns>
public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire) public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire)
{ {
if (_concurrentCollection.TryGetValue(key.Key, out var data)) //little performance workaround here:
return (T)data; //we use "PerRequestCache" to cache a loaded object in memory for the current HTTP request.
//this way we won't connect to Redis server many times per HTTP request (e.g. each time to load a locale or setting)
if (_perRequestCache.IsSet(key.Key))
return _perRequestCache.Get(key.Key, () => default(T));
var lazy = _ongoing.GetOrAdd(key.Key, _ => new(async () => await acquire(), true)); if (key.CacheTime <= 0)
var setTask = Task.CompletedTask; return await acquire();
try var (isSet, item) = await TryGetItemAsync<T>(key);
{
if (lazy.IsValueCreated)
return (T)await lazy.Value;
var (isSet, item) = await TryGetItemAsync<T>(key.Key); if (isSet)
if (!isSet)
{
item = (T)await lazy.Value;
if (key.CacheTime == 0 || item == null)
return item; return item;
setTask = _distributedCache.SetStringAsync( var result = await acquire();
key.Key,
JsonConvert.SerializeObject(item),
PrepareEntryOptions(key));
}
SetLocal(key.Key, item); if (result != null)
await SetAsync(key, result);
return item; return result;
}
finally
{
_ = setTask.ContinueWith(_ => _ongoing.TryRemove(new KeyValuePair<string, Lazy<Task<object>>>(key.Key, lazy)));
}
} }
/// <summary> /// <summary>
@ -205,31 +182,76 @@ public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheMan
/// A task that represents the asynchronous operation /// A task that represents the asynchronous operation
/// The task result contains the cached value associated with the specified key /// The task result contains the cached value associated with the specified key
/// </returns> /// </returns>
public Task<T> GetAsync<T>(CacheKey key, Func<T> acquire) public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)
{ {
return GetAsync(key, () => Task.FromResult(acquire())); //little performance workaround here:
} //we use "PerRequestCache" to cache a loaded object in memory for the current HTTP request.
//this way we won't connect to Redis server many times per HTTP request (e.g. each time to load a locale or setting)
if (_perRequestCache.IsSet(key.Key))
return _perRequestCache.Get(key.Key, () => default(T));
public async Task<T> GetAsync<T>(CacheKey key, T defaultValue = default) if (key.CacheTime <= 0)
{ return acquire();
var value = await _distributedCache.GetStringAsync(key.Key);
return value != null var (isSet, item) = await TryGetItemAsync<T>(key);
? JsonConvert.DeserializeObject<T>(value)
: defaultValue; if (isSet)
return item;
var result = acquire();
if (result != null)
await SetAsync(key, result);
return result;
} }
/// <summary> /// <summary>
/// Get a cached item as an <see cref="object"/> instance, or null on a cache miss. /// Get a cached item. If it's not in the cache yet, then load and cache it
/// </summary> /// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
/// <returns> /// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// A task that represents the asynchronous operation /// <returns>The cached value associated with the specified key</returns>
/// The task result contains the cached value associated with the specified key, or null if none was found public T Get<T>(CacheKey key, Func<T> acquire)
/// </returns>
public async Task<object> GetAsync(CacheKey key)
{ {
return await GetAsync<object>(key); //little performance workaround here:
//we use "PerRequestCache" to cache a loaded object in memory for the current HTTP request.
//this way we won't connect to Redis server many times per HTTP request (e.g. each time to load a locale or setting)
if (_perRequestCache.IsSet(key.Key))
return _perRequestCache.Get(key.Key, () => default(T));
if (key.CacheTime <= 0)
return acquire();
var (isSet, item) = TryGetItem<T>(key);
if (isSet)
return item;
var result = acquire();
if (result != null)
Set(key, result);
return result;
}
/// <summary>
/// Remove the value with the specified key from the cache
/// </summary>
/// <param name="cacheKey">Cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>A task that represents the asynchronous operation</returns>
public async Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
{
cacheKey = PrepareKey(cacheKey, cacheKeyParameters);
await _distributedCache.RemoveAsync(cacheKey.Key);
_perRequestCache.Remove(cacheKey.Key);
using var _ = await _locker.LockAsync();
_keys.Remove(cacheKey.Key);
} }
/// <summary> /// <summary>
@ -238,25 +260,16 @@ public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheMan
/// <param name="key">Key of cached item</param> /// <param name="key">Key of cached item</param>
/// <param name="data">Value for caching</param> /// <param name="data">Value for caching</param>
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
public async Task SetAsync<T>(CacheKey key, T data) public async Task SetAsync(CacheKey key, object data)
{ {
if (data == null || (key?.CacheTime ?? 0) <= 0) if ((key?.CacheTime ?? 0) <= 0 || data == null)
return; return;
var lazy = new Lazy<Task<object>>(() => Task.FromResult(data as object), true);
try
{
_ongoing.TryAdd(key.Key, lazy);
// await the lazy task in order to force value creation instead of directly setting data
// this way, other cache manager instances can access it while it is being set
SetLocal(key.Key, await lazy.Value);
await _distributedCache.SetStringAsync(key.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key)); await _distributedCache.SetStringAsync(key.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));
} _perRequestCache.Set(key.Key, data);
finally
{ using var _ = await _locker.LockAsync();
_ongoing.TryRemove(new KeyValuePair<string, Lazy<Task<object>>>(key.Key, lazy)); _keys.Add(key.Key);
}
} }
/// <summary> /// <summary>
@ -265,19 +278,222 @@ public abstract class DistributedCacheManager : CacheKeyService, IStaticCacheMan
/// <param name="prefix">Cache key prefix</param> /// <param name="prefix">Cache key prefix</param>
/// <param name="prefixParameters">Parameters to create cache key prefix</param> /// <param name="prefixParameters">Parameters to create cache key prefix</param>
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
public abstract Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters); public async Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)
{
prefix = PrepareKeyPrefix(prefix, prefixParameters);
_perRequestCache.RemoveByPrefix(prefix);
using var _ = await _locker.LockAsync();
foreach (var key in _keys.Where(key => key.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)).ToList())
{
await _distributedCache.RemoveAsync(key);
_keys.Remove(key);
}
}
/// <summary> /// <summary>
/// Clear all cache data /// Clear all cache data
/// </summary> /// </summary>
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
public abstract Task ClearAsync(); public async Task ClearAsync()
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{ {
GC.SuppressFinalize(this); //we can't use _perRequestCache.Clear(),
//because HttpContext stores some server data that we should not delete
foreach (var redisKey in _keys)
_perRequestCache.Remove(redisKey);
using var _ = await _locker.LockAsync();
foreach (var key in _keys)
await _distributedCache.RemoveAsync(key);
_keys.Clear();
}
/// <summary>
/// Perform some action with exclusive lock
/// </summary>
/// <param name="resource">The key we are locking on</param>
/// <param name="expirationTime">The time after which the lock will automatically be expired</param>
/// <param name="action">Action to be performed with locking</param>
/// <returns>True if lock was acquired and action was performed; otherwise false</returns>
public bool PerformActionWithLock(string resource, TimeSpan expirationTime, Action action)
{
//ensure that lock is acquired
if (!string.IsNullOrEmpty(_distributedCache.GetString(resource)))
return false;
try
{
_distributedCache.SetString(resource, resource, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expirationTime
});
//perform action
action();
return true;
}
finally
{
//release lock even if action fails
_distributedCache.Remove(resource);
}
}
#endregion
#region Nested class
/// <summary>
/// Represents a manager for caching during an HTTP request (short term caching)
/// </summary>
protected class PerRequestCache
{
#region Fields
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ReaderWriterLockSlim _lockSlim;
#endregion
#region Ctor
public PerRequestCache(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_lockSlim = new ReaderWriterLockSlim();
}
#endregion
#region Utilities
/// <summary>
/// Get a key/value collection that can be used to share data within the scope of this request
/// </summary>
protected virtual IDictionary<object, object> GetItems()
{
return _httpContextAccessor.HttpContext?.Items;
}
#endregion
#region Methods
/// <summary>
/// Get a cached item. If it's not in the cache yet, then load and cache it
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param>
/// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// <returns>The cached value associated with the specified key</returns>
public virtual T Get<T>(string key, Func<T> acquire)
{
IDictionary<object, object> items;
using (new ReaderWriteLockDisposable(_lockSlim, ReaderWriteLockType.Read))
{
items = GetItems();
if (items == null)
return acquire();
//item already is in cache, so return it
if (items[key] != null)
return (T)items[key];
}
//or create it using passed function
var result = acquire();
//and set in cache (if cache time is defined)
using (new ReaderWriteLockDisposable(_lockSlim))
items[key] = result;
return result;
}
/// <summary>
/// Add the specified key and object to the cache
/// </summary>
/// <param name="key">Key of cached item</param>
/// <param name="data">Value for caching</param>
public virtual void Set(string key, object data)
{
if (data == null)
return;
using (new ReaderWriteLockDisposable(_lockSlim))
{
var items = GetItems();
if (items == null)
return;
items[key] = data;
}
}
/// <summary>
/// Get a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">Key of cached item</param>
/// <returns>True if item already is in cache; otherwise false</returns>
public virtual bool IsSet(string key)
{
using (new ReaderWriteLockDisposable(_lockSlim, ReaderWriteLockType.Read))
{
var items = GetItems();
return items?[key] != null;
}
}
/// <summary>
/// Remove the value with the specified key from the cache
/// </summary>
/// <param name="key">Key of cached item</param>
public virtual void Remove(string key)
{
using (new ReaderWriteLockDisposable(_lockSlim))
{
var items = GetItems();
items?.Remove(key);
}
}
/// <summary>
/// Remove items by key prefix
/// </summary>
/// <param name="prefix">String key prefix</param>
public virtual void RemoveByPrefix(string prefix)
{
using (new ReaderWriteLockDisposable(_lockSlim, ReaderWriteLockType.UpgradeableRead))
{
var items = GetItems();
if (items == null)
return;
//get cache keys that matches pattern
var regex = new Regex(prefix,
RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
var matchesKeys = items.Keys.Select(p => p.ToString())
.Where(key => regex.IsMatch(key ?? string.Empty)).ToList();
if (!matchesKeys.Any())
return;
using (new ReaderWriteLockDisposable(_lockSlim))
//remove matching values
foreach (var key in matchesKeys)
items.Remove(key);
}
} }
#endregion #endregion
} }
#endregion
}
}

View File

@ -1,36 +0,0 @@
namespace Nop.Core.Caching;
/// <summary>
/// Represents a cache key manager
/// </summary>
public partial interface ICacheKeyManager
{
/// <summary>
/// Add the key
/// </summary>
/// <param name="key">The key to add</param>
void AddKey(string key);
/// <summary>
/// Remove the key
/// </summary>
/// <param name="key">The key to remove</param>
void RemoveKey(string key);
/// <summary>
/// Remove all keys
/// </summary>
void Clear();
/// <summary>
/// Remove keys by prefix
/// </summary>
/// <param name="prefix">Prefix to delete keys</param>
/// <returns>The list of removed keys</returns>
IEnumerable<string> RemoveByPrefix(string prefix);
/// <summary>
/// The list of keys
/// </summary>
IEnumerable<string> Keys { get; }
}

View File

@ -1,23 +0,0 @@
namespace Nop.Core.Caching;
/// <summary>
/// Cache key service interface
/// </summary>
public partial interface ICacheKeyService
{
/// <summary>
/// Create a copy of cache key and fills it by passed parameters
/// </summary>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>Cache key</returns>
CacheKey PrepareKey(CacheKey cacheKey, params object[] cacheKeyParameters);
/// <summary>
/// Create a copy of cache key using the default cache time and fills it by passed parameters
/// </summary>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>Cache key</returns>
CacheKey PrepareKeyForDefaultCache(CacheKey cacheKey, params object[] cacheKeyParameters);
}

View File

@ -1,43 +1,16 @@
namespace Nop.Core.Caching; using System;
public partial interface ILocker namespace Nop.Core.Caching
{
public interface ILocker
{ {
/// <summary> /// <summary>
/// Performs some asynchronous task with exclusive lock /// Perform some action with exclusive lock
/// </summary> /// </summary>
/// <param name="resource">The key we are locking on</param> /// <param name="resource">The key we are locking on</param>
/// <param name="expirationTime">The time after which the lock will automatically be expired</param> /// <param name="expirationTime">The time after which the lock will automatically be expired</param>
/// <param name="action">Asynchronous task to be performed with locking</param> /// <param name="action">Action to be performed with locking</param>
/// <returns>A task that resolves true if lock was acquired and action was performed; otherwise false</returns> /// <returns>True if lock was acquired and action was performed; otherwise false</returns>
Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action); bool PerformActionWithLock(string resource, TimeSpan expirationTime, Action action);
}
/// <summary>
/// Starts a background task with "heartbeat": a status flag that will be periodically updated to signal to
/// others that the task is running and stop them from starting the same task.
/// </summary>
/// <param name="key">The key of the background task</param>
/// <param name="expirationTime">The time after which the heartbeat key will automatically be expired. Should be longer than <paramref name="heartbeatInterval"/></param>
/// <param name="heartbeatInterval">The interval at which to update the heartbeat, if required by the implementation</param>
/// <param name="action">Asynchronous background task to be performed</param>
/// <param name="cancellationTokenSource">A CancellationTokenSource for manually canceling the task</param>
/// <returns>A task that resolves true if lock was acquired and action was performed; otherwise false</returns>
Task RunWithHeartbeatAsync(string key, TimeSpan expirationTime, TimeSpan heartbeatInterval, Func<CancellationToken, Task> action, CancellationTokenSource cancellationTokenSource = default);
/// <summary>
/// Tries to cancel a background task by flagging it for cancellation on the next heartbeat.
/// </summary>
/// <param name="key">The task's key</param>
/// <param name="expirationTime">The time after which the task will be considered stopped due to system shutdown or other causes,
/// even if not explicitly canceled.</param>
/// <returns>A task that represents requesting cancellation of the task. Note that the completion of this task does not
/// necessarily imply that the task has been canceled, only that cancellation has been requested.</returns>
Task CancelTaskAsync(string key, TimeSpan expirationTime);
/// <summary>
/// Check if a background task is running.
/// </summary>
/// <param name="key">The task's key</param>
/// <returns>A task that resolves to true if the background task is running; otherwise false</returns>
Task<bool> IsTaskRunningAsync(string key);
} }

View File

@ -1,34 +0,0 @@
namespace Nop.Core.Caching;
/// <summary>
/// Represents a manager for caching during an HTTP request (short term caching)
/// </summary>
public partial interface IShortTermCacheManager : ICacheKeyService
{
/// <summary>
/// Remove items by cache key prefix
/// </summary>
/// <param name="prefix">Cache key prefix</param>
/// <param name="prefixParameters">Parameters to create cache key prefix</param>
void RemoveByPrefix(string prefix, params object[] prefixParameters);
/// <summary>
/// Remove the value with the specified key from the cache
/// </summary>
/// <param name="cacheKey">Cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
void Remove(string cacheKey, params object[] cacheKeyParameters);
/// <summary>
/// Get a cached item. If it's not in the cache yet, then load and cache it
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// /// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the cached value associated with the specified key
/// </returns>
Task<T> GetAsync<T>(Func<Task<T>> acquire, CacheKey cacheKey, params object[] cacheKeyParameters);
}

View File

@ -1,9 +1,12 @@
namespace Nop.Core.Caching; using System;
using System.Threading.Tasks;
namespace Nop.Core.Caching
{
/// <summary> /// <summary>
/// Represents a manager for caching between HTTP requests (long term caching) /// Represents a manager for caching between HTTP requests (long term caching)
/// </summary> /// </summary>
public partial interface IStaticCacheManager : IDisposable, ICacheKeyService public interface IStaticCacheManager : IDisposable
{ {
/// <summary> /// <summary>
/// Get a cached item. If it's not in the cache yet, then load and cache it /// Get a cached item. If it's not in the cache yet, then load and cache it
@ -30,26 +33,13 @@ public partial interface IStaticCacheManager : IDisposable, ICacheKeyService
Task<T> GetAsync<T>(CacheKey key, Func<T> acquire); Task<T> GetAsync<T>(CacheKey key, Func<T> acquire);
/// <summary> /// <summary>
/// Get a cached item. If it's not in the cache yet, return a default value /// Get a cached item. If it's not in the cache yet, then load and cache it
/// </summary> /// </summary>
/// <typeparam name="T">Type of cached item</typeparam> /// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
/// <param name="defaultValue">A default value to return if the key is not present in the cache</param> /// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// <returns> /// <returns>The cached value associated with the specified key</returns>
/// A task that represents the asynchronous operation T Get<T>(CacheKey key, Func<T> acquire);
/// The task result contains the cached value associated with the specified key, or the default value if none was found
/// </returns>
Task<T> GetAsync<T>(CacheKey key, T defaultValue = default);
/// <summary>
/// Get a cached item as an <see cref="object"/> instance, or null on a cache miss.
/// </summary>
/// <param name="key">Cache key</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the cached value associated with the specified key, or null if none was found
/// </returns>
Task<object> GetAsync(CacheKey key);
/// <summary> /// <summary>
/// Remove the value with the specified key from the cache /// Remove the value with the specified key from the cache
@ -65,7 +55,7 @@ public partial interface IStaticCacheManager : IDisposable, ICacheKeyService
/// <param name="key">Key of cached item</param> /// <param name="key">Key of cached item</param>
/// <param name="data">Value for caching</param> /// <param name="data">Value for caching</param>
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
Task SetAsync<T>(CacheKey key, T data); Task SetAsync(CacheKey key, object data);
/// <summary> /// <summary>
/// Remove items by cache key prefix /// Remove items by cache key prefix
@ -80,4 +70,33 @@ public partial interface IStaticCacheManager : IDisposable, ICacheKeyService
/// </summary> /// </summary>
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
Task ClearAsync(); Task ClearAsync();
#region Cache key
/// <summary>
/// Create a copy of cache key and fills it by passed parameters
/// </summary>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>Cache key</returns>
CacheKey PrepareKey(CacheKey cacheKey, params object[] cacheKeyParameters);
/// <summary>
/// Create a copy of cache key using the default cache time and fills it by passed parameters
/// </summary>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>Cache key</returns>
CacheKey PrepareKeyForDefaultCache(CacheKey cacheKey, params object[] cacheKeyParameters);
/// <summary>
/// Create a copy of cache key using the short cache time and fills it by passed parameters
/// </summary>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>Cache key</returns>
CacheKey PrepareKeyForShortTermCache(CacheKey cacheKey, params object[] cacheKeyParameters);
#endregion
}
} }

View File

@ -1,10 +0,0 @@
using Microsoft.Extensions.Caching.Memory;
namespace Nop.Core.Caching;
/// <summary>
/// Represents a local in-memory cache with distributed synchronization
/// </summary>
public partial interface ISynchronizedMemoryCache : IMemoryCache
{
}

View File

@ -1,122 +0,0 @@
using Microsoft.Extensions.Caching.Memory;
namespace Nop.Core.Caching;
/// <summary>
/// A distributed cache manager that locks the acquisition task
/// </summary>
public partial class MemoryCacheLocker : ILocker
{
#region Fields
protected readonly IMemoryCache _memoryCache;
#endregion
#region Ctor
public MemoryCacheLocker(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
#endregion
#region Utilities
/// <summary>
/// Run action
/// </summary>
/// <param name="key">The key of the background task</param>
/// <param name="expirationTime">The time after which the lock will automatically be expired</param>
/// <param name="action">The action to perform</param>
/// <param name="cancellationTokenSource">A CancellationTokenSource for manually canceling the task</param>
/// <returns></returns>
protected virtual async Task<bool> RunAsync(string key, TimeSpan? expirationTime, Func<CancellationToken, Task> action, CancellationTokenSource cancellationTokenSource = default)
{
var started = false;
try
{
var tokenSource = _memoryCache.GetOrCreate(key, entry => new Lazy<CancellationTokenSource>(() =>
{
entry.AbsoluteExpirationRelativeToNow = expirationTime;
entry.SetPriority(CacheItemPriority.NeverRemove);
started = true;
return cancellationTokenSource ?? new CancellationTokenSource();
}, true))?.Value;
if (tokenSource != null && started)
await action(tokenSource.Token);
}
catch (OperationCanceledException) { }
finally
{
if (started)
_memoryCache.Remove(key);
}
return started;
}
#endregion
#region Methods
/// <summary>
/// Performs some asynchronous task with exclusive lock
/// </summary>
/// <param name="resource">The key we are locking on</param>
/// <param name="expirationTime">The time after which the lock will automatically be expired</param>
/// <param name="action">Asynchronous task to be performed with locking</param>
/// <returns>A task that resolves true if lock was acquired and action was performed; otherwise false</returns>
public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)
{
return await RunAsync(resource, expirationTime, _ => action());
}
/// <summary>
/// Starts a background task with "heartbeat": a status flag that will be periodically updated to signal to
/// others that the task is running and stop them from starting the same task.
/// </summary>
/// <param name="key">The key of the background task</param>
/// <param name="expirationTime">The time after which the heartbeat key will automatically be expired. Should be longer than <paramref name="heartbeatInterval"/></param>
/// <param name="heartbeatInterval">The interval at which to update the heartbeat, if required by the implementation</param>
/// <param name="action">Asynchronous background task to be performed</param>
/// <param name="cancellationTokenSource">A CancellationTokenSource for manually canceling the task</param>
/// <returns>A task that resolves true if lock was acquired and action was performed; otherwise false</returns>
public async Task RunWithHeartbeatAsync(string key, TimeSpan expirationTime, TimeSpan heartbeatInterval, Func<CancellationToken, Task> action, CancellationTokenSource cancellationTokenSource = default)
{
// We ignore expirationTime and heartbeatInterval here, as the cache is not shared with other instances,
// and will be cleared on system failure anyway. The task is guaranteed to still be running as long as it is in the cache.
await RunAsync(key, null, action, cancellationTokenSource);
}
/// <summary>
/// Tries to cancel a background task by flagging it for cancellation on the next heartbeat.
/// </summary>
/// <param name="key">The task's key</param>
/// <param name="expirationTime">The time after which the task will be considered stopped due to system shutdown or other causes,
/// even if not explicitly canceled.</param>
/// <returns>A task that represents requesting cancellation of the task. Note that the completion of this task does not
/// necessarily imply that the task has been canceled, only that cancellation has been requested.</returns>
public Task CancelTaskAsync(string key, TimeSpan expirationTime)
{
if (_memoryCache.TryGetValue(key, out Lazy<CancellationTokenSource> tokenSource))
tokenSource.Value.Cancel();
return Task.CompletedTask;
}
/// <summary>
/// Check if a background task is running.
/// </summary>
/// <param name="key">The task's key</param>
/// <returns>A task that resolves to true if the background task is running; otherwise false</returns>
public Task<bool> IsTaskRunningAsync(string key)
{
return Task.FromResult(_memoryCache.TryGetValue(key, out _));
}
#endregion
}

View File

@ -1,40 +1,36 @@
using Microsoft.Extensions.Caching.Memory; using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Nop.Core.Configuration; using Nop.Core.Configuration;
namespace Nop.Core.Caching; namespace Nop.Core.Caching
{
/// <summary> /// <summary>
/// Represents a memory cache manager /// Represents a memory cache manager
/// </summary> /// </summary>
/// <remarks> public partial class MemoryCacheManager : CacheKeyService, ILocker, IStaticCacheManager
/// This class should be registered on IoC as singleton instance
/// </remarks>
public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
{ {
#region Fields #region Fields
// Flag: Has Dispose already been called? // Flag: Has Dispose already been called?
protected bool _disposed; private bool _disposed;
protected readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
/// <summary> private static readonly ConcurrentDictionary<string, CancellationTokenSource> _prefixes = new();
/// Holds the keys known by this nopCommerce instance private static CancellationTokenSource _clearToken = new();
/// </summary>
protected readonly ICacheKeyManager _keyManager;
protected static CancellationTokenSource _clearToken = new();
#endregion #endregion
#region Ctor #region Ctor
public MemoryCacheManager(AppSettings appSettings, IMemoryCache memoryCache, ICacheKeyManager cacheKeyManager) public MemoryCacheManager(AppSettings appSettings, IMemoryCache memoryCache) : base(appSettings)
: base(appSettings)
{ {
_memoryCache = memoryCache; _memoryCache = memoryCache;
_keyManager = cacheKeyManager;
} }
#endregion #endregion
@ -46,7 +42,7 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
/// </summary> /// </summary>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
/// <returns>Cache entry options</returns> /// <returns>Cache entry options</returns>
protected virtual MemoryCacheEntryOptions PrepareEntryOptions(CacheKey key) private MemoryCacheEntryOptions PrepareEntryOptions(CacheKey key)
{ {
//set expiration time for the passed cache key //set expiration time for the passed cache key
var options = new MemoryCacheEntryOptions var options = new MemoryCacheEntryOptions
@ -54,35 +50,39 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime) AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)
}; };
//add token to clear cache entries //add tokens to clear cache entries
options.AddExpirationToken(new CancellationChangeToken(_clearToken.Token)); options.AddExpirationToken(new CancellationChangeToken(_clearToken.Token));
options.RegisterPostEvictionCallback(OnEviction); foreach (var keyPrefix in key.Prefixes.ToList())
_keyManager.AddKey(key.Key); {
var tokenSource = _prefixes.GetOrAdd(keyPrefix, new CancellationTokenSource());
options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token));
}
return options; return options;
} }
/// <summary> /// <summary>
/// The callback method which gets called when a cache entry expires. /// Remove the value with the specified key from the cache
/// </summary> /// </summary>
/// <param name="key">The key of the entry being evicted.</param> /// <param name="cacheKey">Cache key</param>
/// <param name="value">The value of the entry being evicted.</param> /// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <param name="reason">The <see cref="EvictionReason"/>.</param> private void Remove(CacheKey cacheKey, params object[] cacheKeyParameters)
/// <param name="state">The information that was passed when registering the callback.</param>
protected virtual void OnEviction(object key, object value, EvictionReason reason, object state)
{ {
switch (reason) cacheKey = PrepareKey(cacheKey, cacheKeyParameters);
{ _memoryCache.Remove(cacheKey.Key);
// we clean up after ourselves elsewhere
case EvictionReason.Removed:
case EvictionReason.Replaced:
case EvictionReason.TokenExpired:
break;
// if the entry was evicted by the cache itself, we remove the key
default:
_keyManager.RemoveKey(key as string);
break;
} }
/// <summary>
/// Add the specified key and object to the cache
/// </summary>
/// <param name="key">Key of cached item</param>
/// <param name="data">Value for caching</param>
private void Set(CacheKey key, object data)
{
if ((key?.CacheTime ?? 0) <= 0 || data == null)
return;
_memoryCache.Set(key.Key, data, PrepareEntryOptions(key));
} }
#endregion #endregion
@ -97,9 +97,7 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
public Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters) public Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
{ {
var key = PrepareKey(cacheKey, cacheKeyParameters).Key; Remove(cacheKey, cacheKeyParameters);
_memoryCache.Remove(key);
_keyManager.RemoveKey(key);
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -119,61 +117,15 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
if ((key?.CacheTime ?? 0) <= 0) if ((key?.CacheTime ?? 0) <= 0)
return await acquire(); return await acquire();
var task = _memoryCache.GetOrCreate( if (_memoryCache.TryGetValue(key.Key, out T result))
key.Key, return result;
entry =>
{
entry.SetOptions(PrepareEntryOptions(key));
return new Lazy<Task<T>>(acquire, true);
});
try result = await acquire();
{
var data = await task!.Value;
//if a cached function return null, remove it from the cache if(result != null)
if (data == null) await SetAsync(key, result);
await RemoveAsync(key);
return data; return result;
}
catch (Exception ex)
{
//if a cached function throws an exception, remove it from the cache
await RemoveAsync(key);
if (ex is NullReferenceException)
return default;
throw;
}
}
/// <summary>
/// Get a cached item. If it's not in the cache yet, return a default value
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param>
/// <param name="defaultValue">A default value to return if the key is not present in the cache</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the cached value associated with the specified key, or the default value if none was found
/// </returns>
public async Task<T> GetAsync<T>(CacheKey key, T defaultValue = default)
{
var value = _memoryCache.Get<Lazy<Task<T>>>(key.Key)?.Value;
try
{
return value != null ? await value : defaultValue;
}
catch
{
//if a cached function throws an exception, remove it from the cache
await RemoveAsync(key);
throw;
}
} }
/// <summary> /// <summary>
@ -188,38 +140,44 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
/// </returns> /// </returns>
public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire) public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)
{ {
return await GetAsync(key, () => Task.FromResult(acquire())); if ((key?.CacheTime ?? 0) <= 0)
return acquire();
var result = _memoryCache.GetOrCreate(key.Key, entry =>
{
entry.SetOptions(PrepareEntryOptions(key));
return acquire();
});
//do not cache null value
if (result == null)
await RemoveAsync(key);
return result;
} }
/// <summary> /// <summary>
/// Get a cached item as an <see cref="object"/> instance, or null on a cache miss. /// Get a cached item. If it's not in the cache yet, then load and cache it
/// </summary> /// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Cache key</param> /// <param name="key">Cache key</param>
/// <returns> /// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// A task that represents the asynchronous operation /// <returns>The cached value associated with the specified key</returns>
/// The task result contains the cached value associated with the specified key, or null if none was found public T Get<T>(CacheKey key, Func<T> acquire)
/// </returns>
public async Task<object> GetAsync(CacheKey key)
{ {
var entry = _memoryCache.Get(key.Key); if ((key?.CacheTime ?? 0) <= 0)
if (entry == null) return acquire();
return null;
try
{
if (entry.GetType().GetProperty("Value")?.GetValue(entry) is not Task task)
return null;
await task; if (_memoryCache.TryGetValue(key.Key, out T result))
return result;
return task.GetType().GetProperty("Result")!.GetValue(task); result = acquire();
}
catch
{
//if a cached function throws an exception, remove it from the cache
await RemoveAsync(key);
throw; if (result != null)
} Set(key, result);
return result;
} }
/// <summary> /// <summary>
@ -228,17 +186,42 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
/// <param name="key">Key of cached item</param> /// <param name="key">Key of cached item</param>
/// <param name="data">Value for caching</param> /// <param name="data">Value for caching</param>
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
public Task SetAsync<T>(CacheKey key, T data) public Task SetAsync(CacheKey key, object data)
{ {
if (data != null && (key?.CacheTime ?? 0) > 0) Set(key, data);
_memoryCache.Set(
key.Key,
new Lazy<Task<T>>(() => Task.FromResult(data), true),
PrepareEntryOptions(key));
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Perform some action with exclusive in-memory lock
/// </summary>
/// <param name="key">The key we are locking on</param>
/// <param name="expirationTime">The time after which the lock will automatically be expired</param>
/// <param name="action">Action to be performed with locking</param>
/// <returns>True if lock was acquired and action was performed; otherwise false</returns>
public bool PerformActionWithLock(string key, TimeSpan expirationTime, Action action)
{
//ensure that lock is acquired
if (_memoryCache.TryGetValue(key, out _))
return false;
try
{
_memoryCache.Set(key, key, expirationTime);
//perform action
action();
return true;
}
finally
{
//release lock even if action fails
_memoryCache.Remove(key);
}
}
/// <summary> /// <summary>
/// Remove items by cache key prefix /// Remove items by cache key prefix
/// </summary> /// </summary>
@ -247,8 +230,11 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
/// <returns>A task that represents the asynchronous operation</returns> /// <returns>A task that represents the asynchronous operation</returns>
public Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters) public Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)
{ {
foreach (var key in _keyManager.RemoveByPrefix(PrepareKeyPrefix(prefix, prefixParameters))) prefix = PrepareKeyPrefix(prefix, prefixParameters);
_memoryCache.Remove(key);
_prefixes.TryRemove(prefix, out var tokenSource);
tokenSource?.Cancel();
tokenSource?.Dispose();
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -261,12 +247,21 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
{ {
_clearToken.Cancel(); _clearToken.Cancel();
_clearToken.Dispose(); _clearToken.Dispose();
_clearToken = new CancellationTokenSource(); _clearToken = new CancellationTokenSource();
_keyManager.Clear();
foreach (var prefix in _prefixes.Keys.ToList())
{
_prefixes.TryRemove(prefix, out var tokenSource);
tokenSource?.Dispose();
}
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Dispose cache manager
/// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@ -280,11 +275,11 @@ public partial class MemoryCacheManager : CacheKeyService, IStaticCacheManager
return; return;
if (disposing) if (disposing)
// don't dispose of the MemoryCache, as it is injected _memoryCache.Dispose();
_clearToken.Dispose();
_disposed = true; _disposed = true;
} }
#endregion #endregion
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Caching; namespace Nop.Core.Caching
{
/// <summary> /// <summary>
/// Represents default values related to caching entities /// Represents default values related to caching entities
/// </summary> /// </summary>
@ -51,3 +51,4 @@ public static partial class NopEntityCacheDefaults<TEntity> where TEntity : Base
/// </summary> /// </summary>
public static string AllPrefix => $"Nop.{EntityTypeName}.all."; public static string AllPrefix => $"Nop.{EntityTypeName}.all.";
} }
}

View File

@ -1,76 +0,0 @@
using Nop.Core.Configuration;
using Nop.Core.Infrastructure;
namespace Nop.Core.Caching;
/// <summary>
/// Represents a per request cache manager
/// </summary>
public partial class PerRequestCacheManager : CacheKeyService, IShortTermCacheManager
{
#region Fields
protected readonly ConcurrentTrie<object> _concurrentCollection;
#endregion
#region Ctor
public PerRequestCacheManager(AppSettings appSettings) : base(appSettings)
{
_concurrentCollection = new ConcurrentTrie<object>();
}
#endregion
#region Methods
/// <summary>
/// Get a cached item. If it's not in the cache yet, then load and cache it
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// /// <param name="acquire">Function to load item if it's not in the cache yet</param>
/// <param name="cacheKey">Initial cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the cached value associated with the specified key
/// </returns>
public async Task<T> GetAsync<T>(Func<Task<T>> acquire, CacheKey cacheKey, params object[] cacheKeyParameters)
{
var key = cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters).Key;
if (_concurrentCollection.TryGetValue(key, out var data))
return (T)data;
var result = await acquire();
if (result != null)
_concurrentCollection.Add(key, result);
return result;
}
/// <summary>
/// Remove items by cache key prefix
/// </summary>
/// <param name="prefix">Cache key prefix</param>
/// <param name="prefixParameters">Parameters to create cache key prefix</param>
public virtual void RemoveByPrefix(string prefix, params object[] prefixParameters)
{
var keyPrefix = PrepareKeyPrefix(prefix, prefixParameters);
_concurrentCollection.Prune(keyPrefix, out _);
}
/// <summary>
/// Remove the value with the specified key from the cache
/// </summary>
/// <param name="cacheKey">Cache key</param>
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
public virtual void Remove(string cacheKey, params object[] cacheKeyParameters)
{
_concurrentCollection.Remove(PrepareKey(new CacheKey(cacheKey), cacheKeyParameters).Key);
}
#endregion
}

View File

@ -1,18 +0,0 @@
using Nop.Core.Configuration;
namespace Nop.Core.Caching;
/// <summary>
/// Represents a memory cache manager with distributed synchronization
/// </summary>
/// <remarks>
/// This class should be registered on IoC as singleton instance
/// </remarks>
public partial class SynchronizedMemoryCacheManager : MemoryCacheManager
{
public SynchronizedMemoryCacheManager(AppSettings appSettings,
ISynchronizedMemoryCache memoryCache,
ICacheKeyManager cacheKeyManager) : base(appSettings, memoryCache, cacheKeyManager)
{
}
}

View File

@ -1,11 +1,14 @@
using System.ComponentModel; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Net; using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Nop.Core.Infrastructure; using Nop.Core.Infrastructure;
namespace Nop.Core; namespace Nop.Core
{
/// <summary> /// <summary>
/// Represents a common helper /// Represents a common helper
/// </summary> /// </summary>
@ -13,20 +16,24 @@ public partial class CommonHelper
{ {
#region Fields #region Fields
//we use regular expression based on RFC 5322 Official Standard (see https://emailregex.com/) //we use EmailValidator from FluentValidation. So let's keep them sync - https://github.com/JeremySkinner/FluentValidation/blob/master/src/FluentValidation/Validators/EmailValidator.cs
private const string EMAIL_EXPRESSION = @"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$"; private const string EMAIL_EXPRESSION = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-||_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+([a-z]+|\d|-|\.{0,1}|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
private static readonly Regex _emailRegex;
#endregion
#region Ctor
static CommonHelper()
{
_emailRegex = new Regex(EMAIL_EXPRESSION, RegexOptions.IgnoreCase);
}
#endregion #endregion
#region Methods #region Methods
/// <summary>
/// Get email validation regex
/// </summary>
/// <returns>Regular expression</returns>
[GeneratedRegex(EMAIL_EXPRESSION, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, "en-US")]
public static partial Regex GetEmailRegex();
/// <summary> /// <summary>
/// Ensures the subscriber email or throw. /// Ensures the subscriber email or throw.
/// </summary> /// </summary>
@ -58,7 +65,7 @@ public partial class CommonHelper
email = email.Trim(); email = email.Trim();
return GetEmailRegex().IsMatch(email); return _emailRegex.IsMatch(email);
} }
/// <summary> /// <summary>
@ -184,13 +191,15 @@ public partial class CommonHelper
/// <param name="value">The value to set the property to.</param> /// <param name="value">The value to set the property to.</param>
public static void SetProperty(object instance, string propertyName, object value) public static void SetProperty(object instance, string propertyName, object value)
{ {
ArgumentNullException.ThrowIfNull(instance); if (instance == null)
ArgumentNullException.ThrowIfNull(propertyName); throw new ArgumentNullException(nameof(instance));
if (propertyName == null)
throw new ArgumentNullException(nameof(propertyName));
var instanceType = instance.GetType(); var instanceType = instance.GetType();
var pi = instanceType.GetProperty(propertyName) var pi = instanceType.GetProperty(propertyName);
?? throw new NopException("No property '{0}' found on the instance of type '{1}'.", propertyName, instanceType); if (pi == null)
throw new NopException("No property '{0}' found on the instance of type '{1}'.", propertyName, instanceType);
if (!pi.CanWrite) if (!pi.CanWrite)
throw new NopException("The property '{0}' on the instance of type '{1}' does not have a setter.", propertyName, instanceType); throw new NopException("The property '{0}' on the instance of type '{1}' does not have a setter.", propertyName, instanceType);
if (value != null && !value.GetType().IsAssignableFrom(pi.PropertyType)) if (value != null && !value.GetType().IsAssignableFrom(pi.PropertyType))
@ -253,22 +262,23 @@ public partial class CommonHelper
} }
/// <summary> /// <summary>
/// Splits the camel-case word into separate one /// Convert enum for front-end
/// </summary> /// </summary>
/// <param name="str">Input string</param> /// <param name="str">Input string</param>
/// <returns>Splitted string</returns> /// <returns>Converted string</returns>
public static string SplitCamelCaseWord(string str) public static string ConvertEnum(string str)
{ {
if (string.IsNullOrEmpty(str)) if (string.IsNullOrEmpty(str))
return string.Empty; return string.Empty;
var result = string.Empty;
var result = str.ToCharArray() foreach (var c in str)
.Select(p => p.ToString()) if (c.ToString() != c.ToString().ToLowerInvariant())
.Aggregate(string.Empty, (current, c) => current + (c == c.ToUpperInvariant() ? $" {c}" : c)); result += " " + c.ToString();
else
result += c.ToString();
//ensure no spaces (e.g. when the first letter is upper case) //ensure no spaces (e.g. when the first letter is upper case)
result = result.TrimStart(); result = result.TrimStart();
return result; return result;
} }
@ -320,3 +330,4 @@ public partial class CommonHelper
#endregion #endregion
} }
}

View File

@ -1,32 +1,35 @@
using System.ComponentModel; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace Nop.Core.ComponentModel; namespace Nop.Core.ComponentModel
{
/// <summary> /// <summary>
/// Generic Dictionary type converted /// Generic Dictionary type converted
/// </summary> /// </summary>
/// <typeparam name="K">Key type (simple)</typeparam> /// <typeparam name="K">Key type (simple)</typeparam>
/// <typeparam name="V">Value type (simple)</typeparam> /// <typeparam name="V">Value type (simple)</typeparam>
public partial class GenericDictionaryTypeConverter<K, V> : TypeConverter public class GenericDictionaryTypeConverter<K, V> : TypeConverter
{ {
/// <summary> /// <summary>
/// Type converter /// Type converter
/// </summary> /// </summary>
protected readonly TypeConverter _typeConverterKey; protected readonly TypeConverter typeConverterKey;
/// <summary> /// <summary>
/// Type converter /// Type converter
/// </summary> /// </summary>
protected readonly TypeConverter _typeConverterValue; protected readonly TypeConverter typeConverterValue;
public GenericDictionaryTypeConverter() public GenericDictionaryTypeConverter()
{ {
_typeConverterKey = TypeDescriptor.GetConverter(typeof(K)); typeConverterKey = TypeDescriptor.GetConverter(typeof(K));
if (_typeConverterKey == null) if (typeConverterKey == null)
throw new InvalidOperationException("No type converter exists for type " + typeof(K).FullName); throw new InvalidOperationException("No type converter exists for type " + typeof(K).FullName);
_typeConverterValue = TypeDescriptor.GetConverter(typeof(V)); typeConverterValue = TypeDescriptor.GetConverter(typeof(V));
if (_typeConverterValue == null) if (typeConverterValue == null)
throw new InvalidOperationException("No type converter exists for type " + typeof(V).FullName); throw new InvalidOperationException("No type converter exists for type " + typeof(V).FullName);
} }
@ -62,20 +65,20 @@ public partial class GenericDictionaryTypeConverter<K, V> : TypeConverter
var items = string.IsNullOrEmpty(input) ? Array.Empty<string>() : input.Split(';').Select(x => x.Trim()).ToArray(); var items = string.IsNullOrEmpty(input) ? Array.Empty<string>() : input.Split(';').Select(x => x.Trim()).ToArray();
var result = new Dictionary<K, V>(); var result = new Dictionary<K, V>();
foreach (var item in items) Array.ForEach(items, s =>
{ {
var keyValueStr = string.IsNullOrEmpty(item) ? Array.Empty<string>() : item.Split(',').Select(x => x.Trim()).ToArray(); var keyValueStr = string.IsNullOrEmpty(s) ? Array.Empty<string>() : s.Split(',').Select(x => x.Trim()).ToArray();
if (keyValueStr.Length != 2) if (keyValueStr.Length != 2)
continue; return;
object dictionaryKey = (K)_typeConverterKey.ConvertFromInvariantString(keyValueStr[0]); object dictionaryKey = (K)typeConverterKey.ConvertFromInvariantString(keyValueStr[0]);
object dictionaryValue = (V)_typeConverterValue.ConvertFromInvariantString(keyValueStr[1]); object dictionaryValue = (V)typeConverterValue.ConvertFromInvariantString(keyValueStr[1]);
if (dictionaryKey == null || dictionaryValue == null) if (dictionaryKey == null || dictionaryValue == null)
continue; return;
if (!result.ContainsKey((K)dictionaryKey)) if (!result.ContainsKey((K)dictionaryKey))
result.Add((K)dictionaryKey, (V)dictionaryValue); result.Add((K)dictionaryKey, (V)dictionaryValue);
} });
return result; return result;
} }
@ -112,3 +115,4 @@ public partial class GenericDictionaryTypeConverter<K, V> : TypeConverter
return result; return result;
} }
} }
}

View File

@ -1,13 +1,16 @@
using System.ComponentModel; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace Nop.Core.ComponentModel; namespace Nop.Core.ComponentModel
{
/// <summary> /// <summary>
/// Generic List type converted /// Generic List type converted
/// </summary> /// </summary>
/// <typeparam name="T">Type</typeparam> /// <typeparam name="T">Type</typeparam>
public partial class GenericListTypeConverter<T> : TypeConverter public class GenericListTypeConverter<T> : TypeConverter
{ {
/// <summary> /// <summary>
/// Type converter /// Type converter
@ -61,11 +64,17 @@ public partial class GenericListTypeConverter<T> : TypeConverter
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);
var items = GetStringArray((string)value); var items = GetStringArray((string)value);
var result = new List<T>();
Array.ForEach(items, s =>
{
var item = typeConverter.ConvertFromInvariantString(s);
if (item != null)
{
result.Add((T)item);
}
});
return items.Select(typeConverter.ConvertFromInvariantString) return result;
.Where(item => item != null)
.Cast<T>()
.ToList();
} }
/// <summary> /// <summary>
@ -82,15 +91,20 @@ public partial class GenericListTypeConverter<T> : TypeConverter
return base.ConvertTo(context, culture, value, destinationType); return base.ConvertTo(context, culture, value, destinationType);
var result = string.Empty; var result = string.Empty;
if (value == null) if (value == null)
return result; return result;
var cultureInvariantStrings = ((IList<T>)value) //we don't use string.Join() because it doesn't support invariant culture
.Select(o => Convert.ToString(o, CultureInfo.InvariantCulture)); for (var i = 0; i < ((IList<T>)value).Count; i++)
{
result = string.Join(',', cultureInvariantStrings); var str1 = Convert.ToString(((IList<T>)value)[i], CultureInfo.InvariantCulture);
result += str1;
//don't add comma after the last element
if (i != ((IList<T>)value).Count - 1)
result += ",";
}
return result; return result;
} }
} }
}

View File

@ -1,22 +1,19 @@
namespace Nop.Core.ComponentModel; using System;
using System.Threading;
namespace Nop.Core.ComponentModel
{
/// <summary> /// <summary>
/// Provides a convenience methodology for implementing locked access to resources. /// Provides a convenience methodology for implementing locked access to resources.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Intended as an infrastructure class. /// Intended as an infrastructure class.
/// </remarks> /// </remarks>
public partial class ReaderWriteLockDisposable : IDisposable public class ReaderWriteLockDisposable : IDisposable
{ {
#region Fields private bool _disposed = false;
private readonly ReaderWriterLockSlim _rwLock;
protected bool _disposed; private readonly ReaderWriteLockType _readerWriteLockType;
protected readonly ReaderWriterLockSlim _rwLock;
protected readonly ReaderWriteLockType _readerWriteLockType;
#endregion
#region Ctor
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ReaderWriteLockDisposable"/> class. /// Initializes a new instance of the <see cref="ReaderWriteLockDisposable"/> class.
@ -42,14 +39,14 @@ public partial class ReaderWriteLockDisposable : IDisposable
} }
} }
#endregion // Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#region Utilities // Protected implementation of Dispose pattern.
/// <summary>
/// Protected implementation of Dispose pattern.
/// </summary>
/// <param name="disposing">Specifies whether to disposing resources</param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (_disposed) if (_disposed)
@ -70,22 +67,7 @@ public partial class ReaderWriteLockDisposable : IDisposable
break; break;
} }
} }
_disposed = true; _disposed = true;
} }
#endregion
#region Methods
/// <summary>
/// Public implementation of Dispose pattern callable by consumers.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} }
#endregion
} }

View File

@ -1,5 +1,5 @@
namespace Nop.Core.ComponentModel; namespace Nop.Core.ComponentModel
{
/// <summary> /// <summary>
/// Reader/Write locker type /// Reader/Write locker type
/// </summary> /// </summary>
@ -9,3 +9,4 @@ public enum ReaderWriteLockType
Write, Write,
UpgradeableRead UpgradeableRead
} }
}

View File

@ -1,8 +1,11 @@
using Newtonsoft.Json; using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents the app settings /// Represents the app settings
/// </summary> /// </summary>
@ -10,7 +13,7 @@ public partial class AppSettings
{ {
#region Fields #region Fields
protected readonly Dictionary<Type, IConfig> _configurations; private readonly Dictionary<Type, IConfig> _configurations = new();
#endregion #endregion
@ -26,6 +29,16 @@ public partial class AppSettings
#endregion #endregion
#region Properties
/// <summary>
/// Gets or sets raw configuration parameters
/// </summary>
[JsonExtensionData]
public Dictionary<string, JToken> Configuration { get; set; }
#endregion
#region Methods #region Methods
/// <summary> /// <summary>
@ -54,14 +67,5 @@ public partial class AppSettings
} }
#endregion #endregion
}
#region Properties
/// <summary>
/// Gets or sets raw configuration parameters
/// </summary>
[JsonExtensionData]
public Dictionary<string, JToken> Configuration { get; set; }
#endregion
} }

View File

@ -1,10 +1,13 @@
using System.Text; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Nop.Core.Infrastructure; using Nop.Core.Infrastructure;
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents the app settings helper /// Represents the app settings helper
/// </summary> /// </summary>
@ -12,7 +15,7 @@ public partial class AppSettingsHelper
{ {
#region Fields #region Fields
protected static Dictionary<string, int> _configurationOrder; private static Dictionary<string, int> _configurationOrder;
#endregion #endregion
@ -27,8 +30,10 @@ public partial class AppSettingsHelper
/// <returns>App settings</returns> /// <returns>App settings</returns>
public static AppSettings SaveAppSettings(IList<IConfig> configurations, INopFileProvider fileProvider, bool overwrite = true) public static AppSettings SaveAppSettings(IList<IConfig> configurations, INopFileProvider fileProvider, bool overwrite = true)
{ {
ArgumentNullException.ThrowIfNull(configurations); if (configurations is null)
throw new ArgumentNullException(nameof(configurations));
if (_configurationOrder is null)
_configurationOrder = configurations.ToDictionary(config => config.Name, config => config.GetOrder()); _configurationOrder = configurations.ToDictionary(config => config.Name, config => config.GetOrder());
//create app settings //create app settings
@ -70,3 +75,4 @@ public partial class AppSettingsHelper
#endregion #endregion
} }
}

View File

@ -1,9 +1,12 @@
namespace Nop.Core.Configuration; using System.Linq;
using System.Collections.Generic;
namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents the event that is raised when App Settings are saving /// Represents the event that is raised when App Settings are saving
/// </summary> /// </summary>
public partial class AppSettingsSavingEvent public class AppSettingsSavingEvent
{ {
#region Ctor #region Ctor
@ -14,6 +17,15 @@ public partial class AppSettingsSavingEvent
#endregion #endregion
#region Properties
/// <summary>
/// Gets configurations to save
/// </summary>
public IList<IConfig> Configurations { get; private set; }
#endregion
#region Methods #region Methods
/// <summary> /// <summary>
@ -29,13 +41,5 @@ public partial class AppSettingsSavingEvent
} }
#endregion #endregion
}
#region Properties
/// <summary>
/// Gets configurations to save
/// </summary>
public IList<IConfig> Configurations { get; protected set; }
#endregion
} }

View File

@ -1,7 +1,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents Azure Blob storage configuration parameters /// Represents Azure Blob storage configuration parameters
/// </summary> /// </summary>
@ -10,37 +10,37 @@ public partial class AzureBlobConfig : IConfig
/// <summary> /// <summary>
/// Gets or sets connection string for Azure Blob storage /// Gets or sets connection string for Azure Blob storage
/// </summary> /// </summary>
public string ConnectionString { get; protected set; } = string.Empty; public string ConnectionString { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets container name for Azure Blob storage /// Gets or sets container name for Azure Blob storage
/// </summary> /// </summary>
public string ContainerName { get; protected set; } = string.Empty; public string ContainerName { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets end point for Azure Blob storage /// Gets or sets end point for Azure Blob storage
/// </summary> /// </summary>
public string EndPoint { get; protected set; } = string.Empty; public string EndPoint { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets whether or the Container Name is appended to the AzureBlobStorageEndPoint when constructing the url /// Gets or sets whether or the Container Name is appended to the AzureBlobStorageEndPoint when constructing the url
/// </summary> /// </summary>
public bool AppendContainerName { get; protected set; } = true; public bool AppendContainerName { get; private set; } = true;
/// <summary> /// <summary>
/// Gets or sets whether to store Data Protection Keys in Azure Blob Storage /// Gets or sets whether to store Data Protection Keys in Azure Blob Storage
/// </summary> /// </summary>
public bool StoreDataProtectionKeys { get; protected set; } = false; public bool StoreDataProtectionKeys { get; private set; } = false;
/// <summary> /// <summary>
/// Gets or sets the Azure container name for storing Data Prtection Keys (this container should be separate from the container used for media and should be Private) /// Gets or sets the Azure container name for storing Data Prtection Keys (this container should be separate from the container used for media and should be Private)
/// </summary> /// </summary>
public string DataProtectionKeysContainerName { get; protected set; } = string.Empty; public string DataProtectionKeysContainerName { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets the Azure key vault ID used to encrypt the Data Protection Keys. (this is optional) /// Gets or sets the Azure key vault ID used to encrypt the Data Protection Keys. (this is optional)
/// </summary> /// </summary>
public string DataProtectionKeysVaultId { get; protected set; } = string.Empty; public string DataProtectionKeysVaultId { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets a value indicating whether we should use Azure Blob storage /// Gets a value indicating whether we should use Azure Blob storage
@ -54,3 +54,4 @@ public partial class AzureBlobConfig : IConfig
[JsonIgnore] [JsonIgnore]
public bool DataProtectionKeysEncryptWithVault => !string.IsNullOrEmpty(DataProtectionKeysVaultId); public bool DataProtectionKeysEncryptWithVault => !string.IsNullOrEmpty(DataProtectionKeysVaultId);
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents cache configuration parameters /// Represents cache configuration parameters
/// </summary> /// </summary>
@ -8,10 +8,16 @@ public partial class CacheConfig : IConfig
/// <summary> /// <summary>
/// Gets or sets the default cache time in minutes /// Gets or sets the default cache time in minutes
/// </summary> /// </summary>
public int DefaultCacheTime { get; protected set; } = 60; public int DefaultCacheTime { get; private set; } = 60;
/// <summary> /// <summary>
/// Gets or sets whether to disable linq2db query cache /// Gets or sets the short term cache time in minutes
/// </summary> /// </summary>
public bool LinqDisableQueryCache { get; protected set; } = false; public int ShortTermCacheTime { get; private set; } = 3;
/// <summary>
/// Gets or sets the bundled files cache time in minutes
/// </summary>
public int BundledFilesCacheTime { get; private set; } = 120;
}
} }

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents common configuration parameters /// Represents common configuration parameters
/// </summary> /// </summary>
@ -8,71 +8,52 @@ public partial class CommonConfig : IConfig
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to display the full error in production environment. It's ignored (always enabled) in development environment /// Gets or sets a value indicating whether to display the full error in production environment. It's ignored (always enabled) in development environment
/// </summary> /// </summary>
public bool DisplayFullErrorStack { get; protected set; } = false; public bool DisplayFullErrorStack { get; private set; } = false;
/// <summary> /// <summary>
/// Gets or sets path to database with user agent strings /// Gets or sets path to database with user agent strings
/// </summary> /// </summary>
public string UserAgentStringsPath { get; protected set; } = "~/App_Data/browscap.xml"; public string UserAgentStringsPath { get; private set; } = "~/App_Data/browscap.xml";
/// <summary> /// <summary>
/// Gets or sets path to database with crawler only user agent strings /// Gets or sets path to database with crawler only user agent strings
/// </summary> /// </summary>
public string CrawlerOnlyUserAgentStringsPath { get; protected set; } = "~/App_Data/browscap.crawlersonly.xml"; public string CrawlerOnlyUserAgentStringsPath { get; private set; } = "~/App_Data/browscap.crawlersonly.xml";
/// <summary>
/// Gets or sets path to additional database with crawler only user agent strings
/// </summary>
public string CrawlerOnlyAdditionalUserAgentStringsPath { get; protected set; } = "~/App_Data/additional.crawlers.xml";
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to store TempData in the session state. By default the cookie-based TempData provider is used to store TempData in cookies. /// Gets or sets a value indicating whether to store TempData in the session state. By default the cookie-based TempData provider is used to store TempData in cookies.
/// </summary> /// </summary>
public bool UseSessionStateTempDataProvider { get; protected set; } = false; public bool UseSessionStateTempDataProvider { get; private set; } = false;
/// <summary>
/// Gets or sets a value that indicates whether to use MiniProfiler services
/// </summary>
public bool MiniProfilerEnabled { get; private set; } = false;
/// <summary> /// <summary>
/// The length of time, in milliseconds, before the running schedule task times out. Set null to use default value /// The length of time, in milliseconds, before the running schedule task times out. Set null to use default value
/// </summary> /// </summary>
public int? ScheduleTaskRunTimeout { get; protected set; } = null; public int? ScheduleTaskRunTimeout { get; private set; } = null;
/// <summary> /// <summary>
/// Gets or sets a value of "Cache-Control" header value for static content (in seconds) /// Gets or sets a value of "Cache-Control" header value for static content (in seconds)
/// </summary> /// </summary>
public string StaticFilesCacheControl { get; protected set; } = "public,max-age=31536000"; public string StaticFilesCacheControl { get; private set; } = "public,max-age=31536000";
/// <summary>
/// Gets or sets a value indicating whether we should support previous nopCommerce versions (it can slightly improve performance)
/// </summary>
public bool SupportPreviousNopcommerceVersions { get; private set; } = true;
/// <summary> /// <summary>
/// Get or set the blacklist of static file extension for plugin directories /// Get or set the blacklist of static file extension for plugin directories
/// </summary> /// </summary>
public string PluginStaticFileExtensionsBlacklist { get; protected set; } = ""; public string PluginStaticFileExtensionsBlacklist { get; private set; } = "";
/// <summary> /// <summary>
/// Get or set a value indicating whether to serve files that don't have a recognized content-type /// Get or set a value indicating whether to serve files that don't have a recognized content-type
/// </summary> /// </summary>
public bool ServeUnknownFileTypes { get; protected set; } = false; /// <value></value>
public bool ServeUnknownFileTypes { get; private set; } = false;
/// <summary> }
/// Get or set a value indicating whether to use Autofac IoC container
///
/// If value is set to false then the default .Net IoC container will be use
/// </summary>
public bool UseAutofac { get; set; } = true;
/// <summary>
/// Maximum number of permit counters that can be allowed in a window (1 minute).
/// Must be set to a value > 0 by the time these options are passed to the constructor of <see cref="FixedWindowRateLimiter"/>.
/// If set to 0 than limitation is off
/// </summary>
public int PermitLimit { get; set; } = 0;
/// <summary>
/// Maximum cumulative permit count of queued acquisition requests.
/// Must be set to a value >= 0 by the time these options are passed to the constructor of <see cref="FixedWindowRateLimiter"/>.
/// If set to 0 than Queue is off
/// </summary>
public int QueueCount { get; set; } = 0;
/// <summary>
/// Default status code to set on the response when a request is rejected.
/// </summary>
public int RejectionStatusCode { get; set; } = 503;
} }

View File

@ -1,8 +1,8 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents distributed cache configuration parameters /// Represents distributed cache configuration parameters
/// </summary> /// </summary>
@ -12,39 +12,26 @@ public partial class DistributedCacheConfig : IConfig
/// Gets or sets a distributed cache type /// Gets or sets a distributed cache type
/// </summary> /// </summary>
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public DistributedCacheType DistributedCacheType { get; protected set; } = DistributedCacheType.RedisSynchronizedMemory; public DistributedCacheType DistributedCacheType { get; private set; } = DistributedCacheType.Redis;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether we should use distributed cache /// Gets or sets a value indicating whether we should use distributed cache
/// </summary> /// </summary>
public bool Enabled { get; protected set; } = false; public bool Enabled { get; private set; } = false;
/// <summary> /// <summary>
/// Gets or sets connection string. Used when distributed cache is enabled /// Gets or sets connection string. Used when distributed cache is enabled
/// </summary> /// </summary>
public string ConnectionString { get; protected set; } = "127.0.0.1:6379,ssl=False"; public string ConnectionString { get; private set; } = "127.0.0.1:6379,ssl=False";
/// <summary> /// <summary>
/// Gets or sets schema name. Used when distributed cache is enabled and DistributedCacheType property is set as SqlServer /// Gets or sets schema name. Used when distributed cache is enabled and DistributedCacheType property is set as SqlServer
/// </summary> /// </summary>
public string SchemaName { get; protected set; } = "dbo"; public string SchemaName { get; private set; } = "dbo";
/// <summary> /// <summary>
/// Gets or sets table name. Used when distributed cache is enabled and DistributedCacheType property is set as SqlServer /// Gets or sets table name. Used when distributed cache is enabled and DistributedCacheType property is set as SqlServer
/// </summary> /// </summary>
public string TableName { get; protected set; } = "DistributedCache"; public string TableName { get; private set; } = "DistributedCache";
}
/// <summary>
/// Gets or sets instance name. Used when distributed cache is enabled and DistributedCacheType property is set as Redis or RedisSynchronizedMemory.
/// Useful when one wants to partition a single Redis server for use with multiple apps, e.g. by setting InstanceName to "development" and "production".
/// </summary>
public string InstanceName { get; protected set; } = "nopCommerce";
/// <summary>
/// Gets or sets the Redis event publish interval in milliseconds.
/// Used when distributed cache is enabled and DistributedCacheType property is set as RedisSynchronizedMemory.
/// If greater than zero, events will be buffered for this long before being published in batch, in order to reduce server load.
/// If zero, events are published when they are raised, without buffering.
/// </summary>
public int PublishIntervalMs { get; protected set; } = 500;
} }

View File

@ -1,7 +1,7 @@
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents distributed cache types enumeration /// Represents distributed cache types enumeration
/// </summary> /// </summary>
@ -12,7 +12,6 @@ public enum DistributedCacheType
[EnumMember(Value = "sqlserver")] [EnumMember(Value = "sqlserver")]
SqlServer, SqlServer,
[EnumMember(Value = "redis")] [EnumMember(Value = "redis")]
Redis, Redis
[EnumMember(Value = "redissynchronizedmemory")] }
RedisSynchronizedMemory
} }

View File

@ -1,5 +1,6 @@
namespace Nop.Core.Configuration; 
namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents hosting configuration parameters /// Represents hosting configuration parameters
/// </summary> /// </summary>
@ -8,25 +9,21 @@ public partial class HostingConfig : IConfig
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to use proxy servers and load balancers /// Gets or sets a value indicating whether to use proxy servers and load balancers
/// </summary> /// </summary>
public bool UseProxy { get; protected set; } public bool UseProxy { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the header used to retrieve the value for the originating scheme (HTTP/HTTPS) /// Gets or sets the header used to retrieve the value for the originating scheme (HTTP/HTTPS)
/// </summary> /// </summary>
public string ForwardedProtoHeaderName { get; protected set; } = string.Empty; public string ForwardedProtoHeaderName { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets the header used to retrieve the originating client IP /// Gets or sets the header used to retrieve the originating client IP
/// </summary> /// </summary>
public string ForwardedForHeaderName { get; protected set; } = string.Empty; public string ForwardedForHeaderName { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets addresses of known proxies to accept forwarded headers from /// Gets or sets addresses of known proxies to accept forwarded headers from
/// </summary> /// </summary>
public string KnownProxies { get; protected set; } = string.Empty; public string KnownProxies { get; private set; } = string.Empty;
}
/// <summary>
/// Gets or sets addresses of known networks to accept forwarded headers from
/// </summary>
public string KnownNetworks { get; protected set; } = string.Empty;
} }

View File

@ -1,7 +1,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents a configuration from app settings /// Represents a configuration from app settings
/// </summary> /// </summary>
@ -19,3 +19,4 @@ public partial interface IConfig
/// <returns>Order</returns> /// <returns>Order</returns>
public int GetOrder() => 1; public int GetOrder() => 1;
} }
}

View File

@ -1,8 +1,9 @@
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Setting interface /// Setting interface
/// </summary> /// </summary>
public partial interface ISettings public interface ISettings
{ {
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents installation configuration parameters /// Represents installation configuration parameters
/// </summary> /// </summary>
@ -8,15 +8,16 @@ public partial class InstallationConfig : IConfig
/// <summary> /// <summary>
/// Gets or sets a value indicating whether a store owner can install sample data during installation /// Gets or sets a value indicating whether a store owner can install sample data during installation
/// </summary> /// </summary>
public bool DisableSampleData { get; protected set; } = false; public bool DisableSampleData { get; private set; } = false;
/// <summary> /// <summary>
/// Gets or sets a list of plugins ignored during nopCommerce installation /// Gets or sets a list of plugins ignored during nopCommerce installation
/// </summary> /// </summary>
public string DisabledPlugins { get; protected set; } = string.Empty; public string DisabledPlugins { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to download and setup the regional language pack during installation /// Gets or sets a value indicating whether to download and setup the regional language pack during installation
/// </summary> /// </summary>
public bool InstallRegionalResources { get; protected set; } = true; public bool InstallRegionalResources { get; private set; } = true;
}
} }

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents default values related to configuration services /// Represents default values related to configuration services
/// </summary> /// </summary>
@ -16,3 +16,4 @@ public static partial class NopConfigurationDefaults
/// <remarks>0 - Environment name</remarks> /// <remarks>0 - Environment name</remarks>
public static string AppSettingsEnvironmentFilePath => "App_Data/appsettings.{0}.json"; public static string AppSettingsEnvironmentFilePath => "App_Data/appsettings.{0}.json";
} }
}

View File

@ -1,12 +1,28 @@
namespace Nop.Core.Configuration; namespace Nop.Core.Configuration
{
/// <summary> /// <summary>
/// Represents plugin configuration parameters /// Represents plugin configuration parameters
/// </summary> /// </summary>
public partial class PluginConfig : IConfig public partial class PluginConfig : IConfig
{ {
/// <summary>
/// Gets or sets a value indicating whether to clear /Plugins/bin directory on application startup
/// </summary>
public bool ClearPluginShadowDirectoryOnStartup { get; private set; } = true;
/// <summary>
/// Gets or sets a value indicating whether to copy "locked" assemblies from /Plugins/bin directory to temporary subdirectories on application startup
/// </summary>
public bool CopyLockedPluginAssembilesToSubdirectoriesOnStartup { get; private set; } = true;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to load an assembly into the load-from context, bypassing some security checks. /// Gets or sets a value indicating whether to load an assembly into the load-from context, bypassing some security checks.
/// </summary> /// </summary>
public bool UseUnsafeLoadAssembly { get; set; } = true; public bool UseUnsafeLoadAssembly { get; private set; } = true;
/// <summary>
/// Gets or sets a value indicating whether to copy plugins library to the /Plugins/bin directory on application startup
/// </summary>
public bool UsePluginsShadowCopy { get; private set; } = true;
}
} }

View File

@ -1,7 +1,7 @@
using Nop.Core.Domain.Common; using Nop.Core.Domain.Common;
namespace Nop.Core.Domain.Affiliates; namespace Nop.Core.Domain.Affiliates
{
/// <summary> /// <summary>
/// Represents an affiliate /// Represents an affiliate
/// </summary> /// </summary>
@ -32,3 +32,4 @@ public partial class Affiliate : BaseEntity, ISoftDeletedEntity
/// </summary> /// </summary>
public bool Active { get; set; } public bool Active { get; set; }
} }
}

View File

@ -1,76 +0,0 @@
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Attributes;
/// <summary>
/// Represents the base class for attributes
/// </summary>
public abstract partial class BaseAttribute : BaseEntity, ILocalizedEntity
{
/// <summary>
/// Gets or sets the name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the attribute is required
/// </summary>
public bool IsRequired { get; set; }
/// <summary>
/// Gets or sets the attribute control type identifier
/// </summary>
public int AttributeControlTypeId { get; set; }
/// <summary>
/// Gets or sets the display order
/// </summary>
public int DisplayOrder { get; set; }
/// <summary>
/// Gets the attribute control type
/// </summary>
public AttributeControlType AttributeControlType
{
get => (AttributeControlType)AttributeControlTypeId;
set => AttributeControlTypeId = (int)value;
}
/// <summary>
/// A value indicating whether this attribute should have values
/// </summary>
public bool ShouldHaveValues
{
get
{
if (AttributeControlType == AttributeControlType.TextBox ||
AttributeControlType == AttributeControlType.MultilineTextbox ||
AttributeControlType == AttributeControlType.Datepicker ||
AttributeControlType == AttributeControlType.FileUpload)
return false;
//other attribute control types support values
return true;
}
}
/// <summary>
/// A value indicating whether this attribute can be used as condition for some other attribute
/// </summary>
public bool CanBeUsedAsCondition
{
get
{
if (AttributeControlType == AttributeControlType.ReadonlyCheckboxes ||
AttributeControlType == AttributeControlType.TextBox ||
AttributeControlType == AttributeControlType.MultilineTextbox ||
AttributeControlType == AttributeControlType.Datepicker ||
AttributeControlType == AttributeControlType.FileUpload)
return false;
//other attribute control types support it
return true;
}
}
}

View File

@ -1,29 +0,0 @@
using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Attributes;
/// <summary>
/// Represents the base class for attribute values
/// </summary>
public abstract partial class BaseAttributeValue : BaseEntity, ILocalizedEntity
{
/// <summary>
/// Gets or sets the name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the value is pre-selected
/// </summary>
public bool IsPreSelected { get; set; }
/// <summary>
/// Gets or sets the display order
/// </summary>
public int DisplayOrder { get; set; }
/// <summary>
/// Gets or sets the attribute identifier
/// </summary>
public int AttributeId { get; set; }
}

View File

@ -1,5 +1,7 @@
namespace Nop.Core.Domain.Blogs; using System;
namespace Nop.Core.Domain.Blogs
{
/// <summary> /// <summary>
/// Represents a blog comment /// Represents a blog comment
/// </summary> /// </summary>
@ -35,3 +37,4 @@ public partial class BlogComment : BaseEntity
/// </summary> /// </summary>
public DateTime CreatedOnUtc { get; set; } public DateTime CreatedOnUtc { get; set; }
} }
}

View File

@ -1,8 +1,9 @@
using Nop.Core.Domain.Seo; using System;
using Nop.Core.Domain.Seo;
using Nop.Core.Domain.Stores; using Nop.Core.Domain.Stores;
namespace Nop.Core.Domain.Blogs; namespace Nop.Core.Domain.Blogs
{
/// <summary> /// <summary>
/// Represents a blog post /// Represents a blog post
/// </summary> /// </summary>
@ -78,3 +79,4 @@ public partial class BlogPost : BaseEntity, ISlugSupported, IStoreMappingSupport
/// </summary> /// </summary>
public DateTime CreatedOnUtc { get; set; } public DateTime CreatedOnUtc { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Blogs; namespace Nop.Core.Domain.Blogs
{
/// <summary> /// <summary>
/// Represents a blog post tag /// Represents a blog post tag
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public partial class BlogPostTag
/// </summary> /// </summary>
public int BlogPostCount { get; set; } public int BlogPostCount { get; set; }
} }
}

View File

@ -1,11 +1,11 @@
using Nop.Core.Configuration; using Nop.Core.Configuration;
namespace Nop.Core.Domain.Blogs; namespace Nop.Core.Domain.Blogs
{
/// <summary> /// <summary>
/// Blog settings /// Blog settings
/// </summary> /// </summary>
public partial class BlogSettings : ISettings public class BlogSettings : ISettings
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether blog is enabled /// Gets or sets a value indicating whether blog is enabled
@ -47,3 +47,4 @@ public partial class BlogSettings : ISettings
/// </summary> /// </summary>
public bool ShowBlogCommentsPerStore { get; set; } public bool ShowBlogCommentsPerStore { get; set; }
} }
}

View File

@ -1,9 +1,9 @@
namespace Nop.Core.Domain.Blogs; namespace Nop.Core.Domain.Blogs
{
/// <summary> /// <summary>
/// Blog post comment approved event /// Blog post comment approved event
/// </summary> /// </summary>
public partial class BlogCommentApprovedEvent public class BlogCommentApprovedEvent
{ {
/// <summary> /// <summary>
/// Ctor /// Ctor
@ -19,3 +19,4 @@ public partial class BlogCommentApprovedEvent
/// </summary> /// </summary>
public BlogComment BlogComment { get; } public BlogComment BlogComment { get; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents an attribute control type /// Represents an attribute control type
/// </summary> /// </summary>
@ -55,3 +55,4 @@ public enum AttributeControlType
/// </summary> /// </summary>
ReadonlyCheckboxes = 50 ReadonlyCheckboxes = 50
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents an attribute value display type when out of stock /// Represents an attribute value display type when out of stock
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public enum AttributeValueOutOfStockDisplayType
/// </summary> /// </summary>
AlwaysDisplay AlwaysDisplay
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents an attribute value type /// Represents an attribute value type
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public enum AttributeValueType
/// </summary> /// </summary>
AssociatedToProduct = 10, AssociatedToProduct = 10,
} }
}

View File

@ -1,5 +1,7 @@
namespace Nop.Core.Domain.Catalog; using System;
namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a back in stock subscription /// Represents a back in stock subscription
/// </summary> /// </summary>
@ -25,3 +27,4 @@ public partial class BackInStockSubscription : BaseEntity
/// </summary> /// </summary>
public DateTime CreatedOnUtc { get; set; } public DateTime CreatedOnUtc { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a backorder mode /// Represents a backorder mode
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public enum BackorderMode
/// </summary> /// </summary>
AllowQtyBelow0AndNotifyCustomer = 2, AllowQtyBelow0AndNotifyCustomer = 2,
} }
}

View File

@ -1,11 +1,12 @@
using Nop.Core.Configuration; using System.Collections.Generic;
using Nop.Core.Configuration;
namespace Nop.Core.Domain.Catalog;
namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Catalog settings /// Catalog settings
/// </summary> /// </summary>
public partial class CatalogSettings : ISettings public class CatalogSettings : ISettings
{ {
public CatalogSettings() public CatalogSettings()
{ {
@ -54,11 +55,6 @@ public partial class CatalogSettings : ISettings
/// </summary> /// </summary>
public bool ShowFreeShippingNotification { get; set; } public bool ShowFreeShippingNotification { get; set; }
/// <summary>
/// Gets or sets a value indicating whether short description should be displayed in product box
/// </summary>
public bool ShowShortDescriptionOnCatalogPages { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether product sorting is enabled /// Gets or sets a value indicating whether product sorting is enabled
/// </summary> /// </summary>
@ -100,7 +96,7 @@ public partial class CatalogSettings : ISettings
public bool ShowShareButton { get; set; } public bool ShowShareButton { get; set; }
/// <summary> /// <summary>
/// Gets or sets a share code (e.g. ShareThis button code) /// Gets or sets a share code (e.g. AddThis button code)
/// </summary> /// </summary>
public string PageShareCode { get; set; } public string PageShareCode { get; set; }
@ -179,26 +175,16 @@ public partial class CatalogSettings : ISettings
/// </summary> /// </summary>
public bool RecentlyViewedProductsEnabled { get; set; } public bool RecentlyViewedProductsEnabled { get; set; }
/// <summary>
/// Gets or sets a number of products on the "New products" page
/// </summary>
public int NewProductsNumber { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether "New products" page is enabled /// Gets or sets a value indicating whether "New products" page is enabled
/// </summary> /// </summary>
public bool NewProductsEnabled { get; set; } public bool NewProductsEnabled { get; set; }
/// <summary>
/// Gets or sets a number of products on the "New products" page
/// </summary>
public int NewProductsPageSize { get; set; }
/// <summary>
/// Gets or sets a value indicating whether customers are allowed to select page size on the "New products" page
/// </summary>
public bool NewProductsAllowCustomersToSelectPageSize { get; set; }
/// <summary>
/// Gets or sets the available customer selectable page size options on the "New products" page
/// </summary>
public string NewProductsPageSizeOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether "Compare products" feature is enabled /// Gets or sets a value indicating whether "Compare products" feature is enabled
/// </summary> /// </summary>
@ -525,12 +511,12 @@ public partial class CatalogSettings : ISettings
public bool DisplayDatePreOrderAvailability { get; set; } public bool DisplayDatePreOrderAvailability { get; set; }
/// <summary> /// <summary>
/// Get or set a value indicating whether to use a standard menu in public store or use Ajax to load a menu /// Get or set a value indicating whether use standart menu in public store or use Ajax to load menu
/// </summary> /// </summary>
public bool UseAjaxLoadMenu { get; set; } public bool UseAjaxLoadMenu { get; set; }
/// <summary> /// <summary>
/// Get or set a value indicating whether to use standard or AJAX products loading (applicable to 'paging', 'filtering', 'view modes') in catalog /// Get or set a value indicating whether use standart or AJAX products loading (applicable to 'paging', 'filtering', 'view modes') in catalog
/// </summary> /// </summary>
public bool UseAjaxCatalogProductsLoading { get; set; } public bool UseAjaxCatalogProductsLoading { get; set; }
@ -549,44 +535,9 @@ public partial class CatalogSettings : ISettings
/// </summary> /// </summary>
public bool EnableSpecificationAttributeFiltering { get; set; } public bool EnableSpecificationAttributeFiltering { get; set; }
/// <summary>
/// Get or set a value indicating whether the "From" prices (based on price adjustments of combinations and attributes) are displayed on catalog pages
/// </summary>
public bool DisplayFromPrices { get; set; }
/// <summary> /// <summary>
/// Gets or sets the attribute value display type when out of stock /// Gets or sets the attribute value display type when out of stock
/// </summary> /// </summary>
public AttributeValueOutOfStockDisplayType AttributeValueOutOfStockDisplayType { get; set; } public AttributeValueOutOfStockDisplayType AttributeValueOutOfStockDisplayType { get; set; }
}
/// <summary>
/// Gets or sets a value indicating whether customer can search with manufacturer name
/// </summary>
public bool AllowCustomersToSearchWithManufacturerName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether customer can search with category name
/// </summary>
public bool AllowCustomersToSearchWithCategoryName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether all pictures will be displayed on catalog pages
/// </summary>
public bool DisplayAllPicturesOnCatalogPages { get; set; }
/// <summary>
/// Gets or sets the identifier of product URL structure type (e.g. '/category-seo-name/product-seo-name' or '/product-seo-name')
/// </summary>
/// <remarks>We have ProductUrlStructureType enum, but we use int value here so that it can be overridden in third-party plugins</remarks>
public int ProductUrlStructureTypeId { get; set; }
/// <summary>
/// Gets or sets an system name of active search provider
/// </summary>
public string ActiveSearchProviderSystemName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether standard search will be used when the search provider throws an exception
/// </summary>
public bool UseStandardSearchWhenSearchProviderThrowsException { get; set; }
} }

View File

@ -1,12 +1,13 @@
using Nop.Core.Domain.Common; using System;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Discounts; using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Security; using Nop.Core.Domain.Security;
using Nop.Core.Domain.Seo; using Nop.Core.Domain.Seo;
using Nop.Core.Domain.Stores; using Nop.Core.Domain.Stores;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a category /// Represents a category
/// </summary> /// </summary>
@ -132,3 +133,4 @@ public partial class Category : BaseEntity, ILocalizedEntity, ISlugSupported, IA
/// </summary> /// </summary>
public bool ManuallyPriceRange { get; set; } public bool ManuallyPriceRange { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a category template /// Represents a category template
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public partial class CategoryTemplate : BaseEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a cross-sell product /// Represents a cross-sell product
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public partial class CrossSellProduct : BaseEntity
/// </summary> /// </summary>
public int ProductId2 { get; set; } public int ProductId2 { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a download activation type /// Represents a download activation type
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public enum DownloadActivationType
/// </summary> /// </summary>
Manually = 10, Manually = 10,
} }
}

View File

@ -1,9 +1,9 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Product review approved event /// Product review approved event
/// </summary> /// </summary>
public partial class ProductReviewApprovedEvent public class ProductReviewApprovedEvent
{ {
/// <summary> /// <summary>
/// Ctor /// Ctor
@ -19,3 +19,4 @@ public partial class ProductReviewApprovedEvent
/// </summary> /// </summary>
public ProductReview ProductReview { get; } public ProductReview ProductReview { get; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a gift card type /// Represents a gift card type
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public enum GiftCardType
/// </summary> /// </summary>
Physical = 1, Physical = 1,
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a low stock activity /// Represents a low stock activity
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public enum LowStockActivity
/// </summary> /// </summary>
Unpublish = 2, Unpublish = 2,
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a method of inventory management /// Represents a method of inventory management
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public enum ManageInventoryMethod
/// </summary> /// </summary>
ManageStockByAttributes = 2, ManageStockByAttributes = 2,
} }
}

View File

@ -1,12 +1,13 @@
using Nop.Core.Domain.Common; using System;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Discounts; using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Security; using Nop.Core.Domain.Security;
using Nop.Core.Domain.Seo; using Nop.Core.Domain.Seo;
using Nop.Core.Domain.Stores; using Nop.Core.Domain.Stores;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a manufacturer /// Represents a manufacturer
/// </summary> /// </summary>
@ -117,3 +118,4 @@ public partial class Manufacturer : BaseEntity, ILocalizedEntity, ISlugSupported
/// </summary> /// </summary>
public bool ManuallyPriceRange { get; set; } public bool ManuallyPriceRange { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a manufacturer template /// Represents a manufacturer template
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public partial class ManufacturerTemplate : BaseEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

View File

@ -1,7 +1,7 @@
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a predefined (default) product attribute value /// Represents a predefined (default) product attribute value
/// </summary> /// </summary>
@ -47,3 +47,4 @@ public partial class PredefinedProductAttributeValue : BaseEntity, ILocalizedEnt
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

View File

@ -1,12 +1,13 @@
using Nop.Core.Domain.Common; using System;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Discounts; using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Security; using Nop.Core.Domain.Security;
using Nop.Core.Domain.Seo; using Nop.Core.Domain.Seo;
using Nop.Core.Domain.Stores; using Nop.Core.Domain.Stores;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product /// Represents a product
/// </summary> /// </summary>
@ -279,6 +280,11 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
/// </summary> /// </summary>
public int TaxCategoryId { get; set; } public int TaxCategoryId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is telecommunications or broadcasting or electronic services
/// </summary>
public bool IsTelecommunicationsOrBroadcastingOrElectronicServices { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating how to manage inventory /// Gets or sets a value indicating how to manage inventory
/// </summary> /// </summary>
@ -360,11 +366,6 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
/// </summary> /// </summary>
public bool AllowAddingOnlyExistingAttributeCombinations { get; set; } public bool AllowAddingOnlyExistingAttributeCombinations { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to display attribute combination images only
/// </summary>
public bool DisplayAttributeCombinationImagesOnly { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this product is returnable (a customer is allowed to submit return request with this product) /// Gets or sets a value indicating whether this product is returnable (a customer is allowed to submit return request with this product)
/// </summary> /// </summary>
@ -465,6 +466,24 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
/// </summary> /// </summary>
public DateTime? MarkAsNewEndDateTimeUtc { get; set; } public DateTime? MarkAsNewEndDateTimeUtc { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this product has tier prices configured
/// <remarks>The same as if we run TierPrices.Count > 0
/// We use this property for performance optimization:
/// if this property is set to false, then we do not need to load tier prices navigation property
/// </remarks>
/// </summary>
public bool HasTierPrices { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this product has discounts applied
/// <remarks>The same as if we run AppliedDiscounts.Count > 0
/// We use this property for performance optimization:
/// if this property is set to false, then we do not need to load Applied Discounts navigation property
/// </remarks>
/// </summary>
public bool HasDiscountsApplied { get; set; }
/// <summary> /// <summary>
/// Gets or sets the weight /// Gets or sets the weight
/// </summary> /// </summary>
@ -594,3 +613,4 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
set => RentalPricePeriodId = (int)value; set => RentalPricePeriodId = (int)value;
} }
} }
}

View File

@ -1,7 +1,7 @@
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product attribute /// Represents a product attribute
/// </summary> /// </summary>
@ -17,3 +17,4 @@ public partial class ProductAttribute : BaseEntity, ILocalizedEntity
/// </summary> /// </summary>
public string Description { get; set; } public string Description { get; set; }
} }
}

View File

@ -1,7 +1,5 @@
using System.ComponentModel; namespace Nop.Core.Domain.Catalog
{
namespace Nop.Core.Domain.Catalog;
/// <summary> /// <summary>
/// Represents a product attribute combination /// Represents a product attribute combination
/// </summary> /// </summary>
@ -52,17 +50,14 @@ public partial class ProductAttributeCombination : BaseEntity
/// </summary> /// </summary>
public int NotifyAdminForQuantityBelow { get; set; } public int NotifyAdminForQuantityBelow { get; set; }
/// <summary>
/// Gets or sets the identifier of picture associated with this combination
/// </summary>
public int PictureId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the minimum stock quantity /// Gets or sets the minimum stock quantity
/// </summary> /// </summary>
public int MinStockQuantity { get; set; } public int MinStockQuantity { get; set; }
}
/// <summary>
/// The field is not used since 4.70 and is left only for the update process
/// use the <see cref="ProductAttributeCombinationPicture"/> instead
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
[Obsolete("The field is not used since 4.70 and is left only for the update process use the ProductAttributeCombinationPicture instead")]
public int? PictureId { get; set; }
} }

View File

@ -1,17 +0,0 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a product attribute combination picture
/// </summary>
public partial class ProductAttributeCombinationPicture : BaseEntity
{
/// <summary>
/// Gets or sets the product attribute combination id
/// </summary>
public int ProductAttributeCombinationId { get; set; }
/// <summary>
/// Gets or sets the identifier of picture associated with this combination
/// </summary>
public int PictureId { get; set; }
}

View File

@ -1,7 +1,7 @@
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product attribute mapping /// Represents a product attribute mapping
/// </summary> /// </summary>
@ -81,3 +81,4 @@ public partial class ProductAttributeMapping : BaseEntity, ILocalizedEntity
set => AttributeControlTypeId = (int)value; set => AttributeControlTypeId = (int)value;
} }
} }
}

View File

@ -1,8 +1,7 @@
using System.ComponentModel; using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Catalog;
namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product attribute value /// Represents a product attribute value
/// </summary> /// </summary>
@ -78,6 +77,11 @@ public partial class ProductAttributeValue : BaseEntity, ILocalizedEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
/// <summary>
/// Gets or sets the picture (identifier) associated with this value. This picture should replace a product main picture once clicked (selected).
/// </summary>
public int PictureId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the attribute value type /// Gets or sets the attribute value type
/// </summary> /// </summary>
@ -86,13 +90,5 @@ public partial class ProductAttributeValue : BaseEntity, ILocalizedEntity
get => (AttributeValueType)AttributeValueTypeId; get => (AttributeValueType)AttributeValueTypeId;
set => AttributeValueTypeId = (int)value; set => AttributeValueTypeId = (int)value;
} }
}
/// <summary>
/// The field is not used since 4.70 and is left only for the update process
/// use the <see cref="ProductAttributeValuePicture"/> instead
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
[Obsolete("The field is not used since 4.70 and is left only for the update process use the ProductAttributeValuePicture instead")]
public int? PictureId { get; set; }
} }

View File

@ -1,17 +0,0 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a product attribute value picture
/// </summary>
public partial class ProductAttributeValuePicture : BaseEntity
{
/// <summary>
/// Gets or sets the product attribute value id
/// </summary>
public int ProductAttributeValueId { get; set; }
/// <summary>
/// Gets or sets the picture (identifier) associated with this value. This picture should replace a product main picture once clicked (selected).
/// </summary>
public int PictureId { get; set; }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product category mapping /// Represents a product category mapping
/// </summary> /// </summary>
@ -25,3 +25,4 @@ public partial class ProductCategory : BaseEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

View File

@ -1,11 +1,11 @@
using Nop.Core.Configuration; using Nop.Core.Configuration;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Product editor settings /// Product editor settings
/// </summary> /// </summary>
public partial class ProductEditorSettings : ISettings public class ProductEditorSettings : ISettings
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether 'Product type' field is shown /// Gets or sets a value indicating whether 'Product type' field is shown
@ -162,6 +162,11 @@ public partial class ProductEditorSettings : ISettings
/// </summary> /// </summary>
public bool DeliveryDate { get; set; } public bool DeliveryDate { get; set; }
/// <summary>
/// Gets or sets a value indicating whether 'Telecommunications, broadcasting and electronic services' field is shown
/// </summary>
public bool TelecommunicationsBroadcastingElectronicServices { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether 'Product availability range' field is shown /// Gets or sets a value indicating whether 'Product availability range' field is shown
/// </summary> /// </summary>
@ -302,3 +307,4 @@ public partial class ProductEditorSettings : ISettings
/// </summary> /// </summary>
public bool StockQuantityHistory { get; set; } public bool StockQuantityHistory { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product manufacturer mapping /// Represents a product manufacturer mapping
/// </summary> /// </summary>
@ -25,3 +25,4 @@ public partial class ProductManufacturer : BaseEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product picture mapping /// Represents a product picture mapping
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public partial class ProductPicture : BaseEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product-product tag mapping class /// Represents a product-product tag mapping class
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public partial class ProductProductTagMapping : BaseEntity
/// </summary> /// </summary>
public int ProductTagId { get; set; } public int ProductTagId { get; set; }
} }
}

View File

@ -1,5 +1,7 @@
namespace Nop.Core.Domain.Catalog; using System;
namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product review /// Represents a product review
/// </summary> /// </summary>
@ -65,3 +67,4 @@ public partial class ProductReview : BaseEntity
/// </summary> /// </summary>
public DateTime CreatedOnUtc { get; set; } public DateTime CreatedOnUtc { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product review helpfulness /// Represents a product review helpfulness
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public partial class ProductReviewHelpfulness : BaseEntity
/// </summary> /// </summary>
public int CustomerId { get; set; } public int CustomerId { get; set; }
} }
}

View File

@ -1,7 +1,7 @@
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product review and review type mapping /// Represents a product review and review type mapping
/// </summary> /// </summary>
@ -22,3 +22,4 @@ public partial class ProductReviewReviewTypeMapping : BaseEntity, ILocalizedEnti
/// </summary> /// </summary>
public int Rating { get; set; } public int Rating { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents the product sorting /// Represents the product sorting
/// </summary> /// </summary>
@ -35,3 +35,4 @@ public enum ProductSortingEnum
/// </summary> /// </summary>
CreatedOn = 15, CreatedOn = 15,
} }
}

View File

@ -1,7 +1,7 @@
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product specification attribute /// Represents a product specification attribute
/// </summary> /// </summary>
@ -51,3 +51,4 @@ public partial class ProductSpecificationAttribute : BaseEntity, ILocalizedEntit
set => AttributeTypeId = (int)value; set => AttributeTypeId = (int)value;
} }
} }
}

View File

@ -1,8 +1,8 @@
using Nop.Core.Domain.Localization; using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Seo; using Nop.Core.Domain.Seo;
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product tag /// Represents a product tag
/// </summary> /// </summary>
@ -13,3 +13,4 @@ public partial class ProductTag : BaseEntity, ILocalizedEntity, ISlugSupported
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product template /// Represents a product template
/// </summary> /// </summary>
@ -25,3 +25,4 @@ public partial class ProductTemplate : BaseEntity
/// </summary> /// </summary>
public string IgnoredProductTypes { get; set; } public string IgnoredProductTypes { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a product type /// Represents a product type
/// </summary> /// </summary>
@ -15,3 +15,4 @@ public enum ProductType
/// </summary> /// </summary>
GroupedProduct = 10, GroupedProduct = 10,
} }
}

View File

@ -1,22 +0,0 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents the product URL structure type enum
/// </summary>
public enum ProductUrlStructureType
{
/// <summary>
/// Product only (e.g. '/product-seo-name')
/// </summary>
Product = 0,
/// <summary>
/// Category (the most nested), then product (e.g. '/category-seo-name/product-seo-name')
/// </summary>
CategoryProduct = 10,
/// <summary>
/// Manufacturer, then product (e.g. '/manufacturer-seo-name/product-seo-name')
/// </summary>
ManufacturerProduct = 20
}

View File

@ -1,22 +0,0 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a product video mapping
/// </summary>
public partial class ProductVideo : BaseEntity
{
/// <summary>
/// Gets or sets the product identifier
/// </summary>
public int ProductId { get; set; }
/// <summary>
/// Gets or sets the video identifier
/// </summary>
public int VideoId { get; set; }
/// <summary>
/// Gets or sets the display order
/// </summary>
public int DisplayOrder { get; set; }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a record to manage product inventory per warehouse /// Represents a record to manage product inventory per warehouse
/// </summary> /// </summary>
@ -25,3 +25,4 @@ public partial class ProductWarehouseInventory : BaseEntity
/// </summary> /// </summary>
public int ReservedQuantity { get; set; } public int ReservedQuantity { get; set; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a recurring product cycle period /// Represents a recurring product cycle period
/// </summary> /// </summary>
@ -25,3 +25,4 @@ public enum RecurringProductCyclePeriod
/// </summary> /// </summary>
Years = 30, Years = 30,
} }
}

View File

@ -1,5 +1,5 @@
namespace Nop.Core.Domain.Catalog; namespace Nop.Core.Domain.Catalog
{
/// <summary> /// <summary>
/// Represents a related product /// Represents a related product
/// </summary> /// </summary>
@ -20,3 +20,4 @@ public partial class RelatedProduct : BaseEntity
/// </summary> /// </summary>
public int DisplayOrder { get; set; } public int DisplayOrder { get; set; }
} }
}

Some files were not shown because too many files have changed in this diff Show More