diff --git a/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs b/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
index 0901d0250b..d8a622fe83 100644
--- a/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
+++ b/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
@@ -21,9 +21,9 @@ namespace Volo.Utils.SolutionTemplating.Building.Steps
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
- if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore"))
+ if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations"))
{
- lines[i] = lines[i].Replace("EntityFrameworkCore", "MongoDB");
+ lines[i] = lines[i].Replace("EntityFrameworkCore.DbMigrations", "MongoDB");
file.SetLines(lines);
return;
}
diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln
index 41eb7f1aeb..b0b7803963 100644
--- a/framework/Volo.Abp.sln
+++ b/framework/Volo.Abp.sln
@@ -228,7 +228,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Tests", "test\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Sqlite", "src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj", "{58CF8957-5045-4F81-884D-72DF48F721CC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Specifications", "src\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj", "{2C621EED-563C-4F81-A75E-50879E173544}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Specifications.Tests", "test\Volo.Abp.Specifications.Tests\Volo.Abp.Specifications.Tests.csproj", "{D078553A-C70C-4F56-B3E2-9C5BA6384C72}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -684,6 +688,14 @@ Global
{3DA9923E-048E-4FE7-9748-3A0194F5D196}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -801,6 +813,8 @@ Global
{92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{58CF8957-5045-4F81-884D-72DF48F721CC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{3DA9923E-048E-4FE7-9748-3A0194F5D196} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {2C621EED-563C-4F81-A75E-50879E173544} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
index 41cb647649..46b59109c7 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
+++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
@@ -17,6 +17,7 @@
+
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
index 2dd39cdfce..101e9134d7 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
@@ -22,7 +22,7 @@ namespace Volo.Abp.Cli
{
options.Commands["help"] = typeof(HelpCommand);
options.Commands["new"] = typeof(NewCommand);
- options.Commands["add"] = typeof(AddCommand);
+ options.Commands["add-module"] = typeof(AddModuleCommand);
options.Commands["login"] = typeof(LoginCommand);
options.Commands["logout"] = typeof(LogoutCommand);
});
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs
index 502ab294e6..854eabea96 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs
@@ -13,7 +13,10 @@ namespace Volo.Abp.Cli
{
public class CliService : ITransientDependency
{
- public static string Version => typeof(AbpCliCoreModule).Assembly.GetFileVersion();
+ //public static string Version => typeof(AbpCliCoreModule).Assembly
+ // .GetFileVersion()
+ // .RemovePostFix(".0");
+ public static string Version => "0.17.0.0";
public ILogger Logger { get; set; }
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs
similarity index 85%
rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommand.cs
rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs
index e5034725ba..6c5209c2e4 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommand.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs
@@ -7,16 +7,16 @@ using Volo.Abp.ProjectModification;
namespace Volo.Abp.Cli.Commands
{
- public class AddCommand : IConsoleCommand, ITransientDependency
+ public class AddModuleCommand : IConsoleCommand, ITransientDependency
{
- public ILogger Logger { get; set; }
+ public ILogger Logger { get; set; }
protected ModuleAdder ModuleAdder { get; }
- public AddCommand(ModuleAdder moduleAdder)
+ public AddModuleCommand(ModuleAdder moduleAdder)
{
ModuleAdder = moduleAdder;
- Logger = NullLogger.Instance;
+ Logger = NullLogger.Instance;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
index 6b77808f55..b1f55b4ba7 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
@@ -21,9 +21,9 @@ namespace Volo.Abp.ProjectBuilding.Building.Steps
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
- if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore"))
+ if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations"))
{
- lines[i] = lines[i].Replace("EntityFrameworkCore", "MongoDB");
+ lines[i] = lines[i].Replace("EntityFrameworkCore.DbMigrations", "MongoDB");
file.SetLines(lines);
return;
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/AbpModuleFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/AbpModuleFinder.cs
new file mode 100644
index 0000000000..ca02137154
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/AbpModuleFinder.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Volo.Abp.ProjectModification
+{
+ public class AbpModuleFinder
+ {
+ public List Find(string csprojFilePath)
+ {
+ var modules = new List();
+
+ var csFiles = GetAllCsFilesUnderDirectory(Path.GetDirectoryName(csprojFilePath), new List());
+
+ foreach (var csFile in csFiles)
+ {
+ try
+ {
+ var root = CSharpSyntaxTree.ParseText(File.ReadAllText(csFile)).GetRoot();
+
+ var namespaceSyntaxs = root.DescendantNodes().OfType();
+
+ foreach (var namespaceSyntax in namespaceSyntaxs)
+ {
+ var classDeclarationSyntaxs = namespaceSyntax.DescendantNodes().OfType();
+ var syntaxsArray = classDeclarationSyntaxs as ClassDeclarationSyntax[] ?? classDeclarationSyntaxs.ToArray();
+
+ foreach (var classDeclaration in syntaxsArray)
+ {
+ var classDerivedFromAbpModule = classDeclaration.BaseList?.Types.FirstOrDefault(t => t.ToString().Contains("AbpModule"));
+
+ if (classDerivedFromAbpModule != null)
+ {
+ modules.Add(csFile);
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ //ignored!
+ }
+ }
+
+ return modules;
+ }
+
+ private static List GetAllCsFilesUnderDirectory(string path, List allCsFileList)
+ {
+ var directory = new DirectoryInfo(path);
+ var files = directory.GetFiles("*.cs").Select(f => f.DirectoryName + "\\" + f.Name).ToList();
+
+ foreach (var s in files)
+ {
+ allCsFileList.Add(s);
+ }
+
+ var directories = directory.GetDirectories().Select(d => path + "\\" + d.Name).ToList();
+
+ foreach (var subDirectory in directories)
+ {
+ allCsFileList = GetAllCsFilesUnderDirectory(subDirectory, allCsFileList);
+ }
+
+ return allCsFileList;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/DependsOnAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/DependsOnAdder.cs
new file mode 100644
index 0000000000..d1dfd86d0a
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/DependsOnAdder.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Volo.Abp.ProjectModification
+{
+ public class DependsOnAdder
+ {
+ public void Add(string path, string module)
+ {
+ ParseModuleNameAndNameSpace(module, out var nameSpace, out var moduleName);
+
+ var file = File.ReadAllText(path);
+
+ var indexOfEndingPublicClass = file.IndexOf("public class", StringComparison.Ordinal);
+
+ var dependsOnAttribute = "[DependsOn(" + moduleName + ")]" + Environment.NewLine + " ";
+
+ file = file.Insert(indexOfEndingPublicClass, dependsOnAttribute);
+ file = file.Insert(0, GetUsingStatement(nameSpace) + Environment.NewLine); //TODO: Add as the last item in the using list!
+
+ File.WriteAllText(path, file);
+ }
+
+ private void ParseModuleNameAndNameSpace(string module, out string nameSpace, out string moduleName)
+ {
+ var words = module?.Split('.');
+
+ if (words == null || words.Length <= 1)
+ {
+ nameSpace = null;
+ moduleName = module;
+ return;
+ }
+
+ moduleName = words[words.Length - 1];
+ nameSpace = string.Join(".", words.Take(words.Length - 1));
+ }
+
+ private string GetUsingStatement(string nameSpace)
+ {
+ return "using " + nameSpace + ";";
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs
index b0e5f2a353..d6da6fea68 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs
@@ -15,12 +15,12 @@ namespace Volo.Abp.ProjectModification
{
public ILogger Logger { get; set; }
- protected ProjectModuleAdder ProjectModuleAdder { get; }
+ protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; }
protected SolutionModuleAdder SolutionModuleAdder { get; }
- public ModuleAdder(ProjectModuleAdder projectModuleAdder, SolutionModuleAdder solutionModuleAdder)
+ public ModuleAdder(ProjectNugetPackageAdder projectNugetPackageAdder, SolutionModuleAdder solutionModuleAdder)
{
- ProjectModuleAdder = projectModuleAdder;
+ ProjectNugetPackageAdder = projectNugetPackageAdder;
SolutionModuleAdder = solutionModuleAdder;
Logger = NullLogger.Instance;
@@ -43,7 +43,7 @@ namespace Volo.Abp.ProjectModification
if (!args.ProjectFile.IsNullOrEmpty())
{
- await ProjectModuleAdder.AddAsync(args.ProjectFile, args.ModuleName);
+ await ProjectNugetPackageAdder.AddAsync(args.ProjectFile, args.ModuleName);
}
if (!args.SolutionFile.IsNullOrEmpty())
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs
new file mode 100644
index 0000000000..09ee9a8042
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs
@@ -0,0 +1,11 @@
+namespace Volo.Abp.ProjectModification
+{
+ public class NpmPackageInfo
+ {
+ public string Name { get; set; }
+
+ public int ApplicationType { get; set; } //TODO: Enum?
+
+ public bool IsPro { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs
new file mode 100644
index 0000000000..59974f23d3
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs
@@ -0,0 +1,13 @@
+namespace Volo.Abp.ProjectModification
+{
+ public class NugetPackageInfo
+ {
+ public string Name { get; set; }
+
+ public string ModuleClass { get; set; }
+
+ public int Target { get; set; } //TODO: Enum?
+
+ public NpmPackageInfo DependedNpmPackage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectModuleAdder.cs
deleted file mode 100644
index 1960da10a5..0000000000
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectModuleAdder.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Threading.Tasks;
-using Volo.Abp.DependencyInjection;
-
-namespace Volo.Abp.ProjectModification
-{
- public class ProjectModuleAdder : ITransientDependency
- {
- public async Task AddAsync(string projectFile, string moduleName)
- {
-
- }
- }
-}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs
new file mode 100644
index 0000000000..e6566ce21f
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs
@@ -0,0 +1,42 @@
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.ProjectModification
+{
+ public class ProjectNpmPackageAdder : ITransientDependency
+ {
+ public ILogger Logger { get; set; }
+
+ public ProjectNpmPackageAdder()
+ {
+ Logger = NullLogger.Instance;
+ }
+
+ public Task AddAsync(string directory, NpmPackageInfo npmPackage)
+ {
+ var packageJsonFilePath = Path.Combine(directory, "package.json");
+ if (!File.Exists(packageJsonFilePath))
+ {
+ return Task.CompletedTask;
+ }
+
+ Logger.LogInformation($"Installing '{npmPackage.Name}' package to the project '{packageJsonFilePath}'...");
+
+ Logger.LogInformation("yarn add " + npmPackage.Name + "...");
+ var procStartInfo = new ProcessStartInfo("cmd.exe", "/C yarn add " + npmPackage.Name);
+ procStartInfo.WindowStyle = ProcessWindowStyle.Normal;
+ Process.Start(procStartInfo).WaitForExit();
+
+ Logger.LogInformation("gulp...");
+ procStartInfo = new ProcessStartInfo("cmd.exe", "/C gulp");
+ procStartInfo.WindowStyle = ProcessWindowStyle.Normal;
+ Process.Start(procStartInfo).WaitForExit();
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs
new file mode 100644
index 0000000000..bf9c0f25f5
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Json;
+
+namespace Volo.Abp.ProjectModification
+{
+ public class ProjectNugetPackageAdder : ITransientDependency
+ {
+ public ILogger Logger { get; set; }
+
+ protected IJsonSerializer JsonSerializer { get; }
+ protected ProjectNpmPackageAdder NpmPackageAdder { get; }
+
+ public ProjectNugetPackageAdder(
+ IJsonSerializer jsonSerializer,
+ ProjectNpmPackageAdder npmPackageAdder)
+ {
+ JsonSerializer = jsonSerializer;
+ NpmPackageAdder = npmPackageAdder;
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task AddAsync(string projectFile, string packageName)
+ {
+ var packageInfo = await FindNugetPackageInfoAsync(packageName);
+ if (packageInfo == null)
+ {
+ return;
+ }
+
+ Logger.LogInformation($"Installing '{packageName}' package to the project '{Path.GetFileNameWithoutExtension(projectFile)}'...");
+ var procStartInfo = new ProcessStartInfo("dotnet", "add package " + packageName);
+ Process.Start(procStartInfo).WaitForExit();
+
+ var moduleFile = new AbpModuleFinder().Find(projectFile).First();
+ new DependsOnAdder().Add(moduleFile, packageInfo.ModuleClass);
+
+ if (packageInfo.DependedNpmPackage != null)
+ {
+ await NpmPackageAdder.AddAsync(Path.GetDirectoryName(projectFile), packageInfo.DependedNpmPackage);
+ }
+
+ Logger.LogInformation("Successfully installed.");
+ }
+
+ private async Task FindNugetPackageInfoAsync(string moduleName)
+ {
+ using (var client = new HttpClient())
+ {
+ var url = "https://localhost:44328/api/app/nugetPackage/byName/?name=" + moduleName;
+
+ var response = await client.GetAsync(url);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ if (response.StatusCode == HttpStatusCode.NotFound)
+ {
+ Logger.LogError($"ERROR: '{moduleName}' module could not be found.");
+ }
+ else
+ {
+ Logger.LogError($"ERROR: Remote server returns '{response.StatusCode}'");
+ }
+
+ return null;
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ return JsonSerializer.Deserialize(responseContent);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj b/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj
new file mode 100644
index 0000000000..97c236cfc5
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Specifications
+ Volo.Abp.Specifications
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs
new file mode 100644
index 0000000000..6f55973be1
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Specifications
+{
+ public class AbpSpecificationsModule : AbpModule
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs
new file mode 100644
index 0000000000..e0d51bd5c3
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the combined specification which indicates that the first specification
+ /// can be satisifed by the given object whereas the second one cannot.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class AndNotSpecification : CompositeSpecification
+ {
+ ///
+ /// Constructs a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ public AndNotSpecification(ISpecification left, ISpecification right) : base(left, right)
+ {
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ var rightExpression = Right.ToExpression();
+
+ var bodyNot = Expression.Not(rightExpression.Body);
+ var bodyNotExpression = Expression.Lambda>(bodyNot, rightExpression.Parameters);
+
+ return Left.ToExpression().And(bodyNotExpression);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs
new file mode 100644
index 0000000000..621287d634
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the combined specification which indicates that both of the given
+ /// specifications should be satisfied by the given object.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class AndSpecification : CompositeSpecification
+ {
+ ///
+ /// Constructs a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ public AndSpecification(ISpecification left, ISpecification right) : base(left, right)
+ {
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return Left.ToExpression().And(Right.ToExpression());
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs
new file mode 100644
index 0000000000..caf6a5f094
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification that can be satisfied by the given object
+ /// in any circumstance.
+ ///
+ /// The type of the object to which the specification is applied.
+ public sealed class AnySpecification : Specification
+ {
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return o => true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs
new file mode 100644
index 0000000000..01c1e46d33
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs
@@ -0,0 +1,30 @@
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the base class for composite specifications.
+ ///
+ /// The type of the object to which the specification is applied.
+ public abstract class CompositeSpecification : Specification, ICompositeSpecification
+ {
+ ///
+ /// Constructs a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ protected CompositeSpecification(ISpecification left, ISpecification right)
+ {
+ Left = left;
+ Right = right;
+ }
+
+ ///
+ /// Gets the first specification.
+ ///
+ public ISpecification Left { get; }
+
+ ///
+ /// Gets the second specification.
+ ///
+ public ISpecification Right { get; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs
new file mode 100644
index 0000000000..dd169d5320
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the extender for Expression[Func[T, bool]] type.
+ /// This is part of the solution which solves
+ /// the expression parameter problem when going to Entity Framework.
+ /// For more information about this solution please refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx.
+ ///
+ public static class ExpressionFuncExtender
+ {
+ private static Expression Compose(this Expression first, Expression second,
+ Func merge)
+ {
+ // build parameter map (from parameters of second to parameters of first)
+ var map = first.Parameters.Select((f, i) => new {f, s = second.Parameters[i]})
+ .ToDictionary(p => p.s, p => p.f);
+
+ // replace parameters in the second lambda expression with parameters from the first
+ var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
+
+ // apply composition of lambda expression bodies to parameters from the first expression
+ return Expression.Lambda(merge(first.Body, secondBody), first.Parameters);
+ }
+
+ ///
+ /// Combines two given expressions by using the AND semantics.
+ ///
+ /// The type of the object.
+ /// The first part of the expression.
+ /// The second part of the expression.
+ /// The combined expression.
+ public static Expression> And(this Expression> first,
+ Expression> second)
+ {
+ return first.Compose(second, Expression.AndAlso);
+ }
+
+ ///
+ /// Combines two given expressions by using the OR semantics.
+ ///
+ /// The type of the object.
+ /// The first part of the expression.
+ /// The second part of the expression.
+ /// The combined expression.
+ public static Expression> Or(this Expression> first,
+ Expression> second)
+ {
+ return first.Compose(second, Expression.OrElse);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs
new file mode 100644
index 0000000000..14c1bff040
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification which is represented by the corresponding
+ /// LINQ expression.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class ExpressionSpecification : Specification
+ {
+ private readonly Expression> _expression;
+
+ ///
+ /// Initializes a new instance of ExpressionSpecification<T> class.
+ ///
+ /// The LINQ expression which represents the current
+ /// specification.
+ public ExpressionSpecification(Expression> expression)
+ {
+ _expression = expression;
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return _expression;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs
new file mode 100644
index 0000000000..2c73409d34
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs
@@ -0,0 +1,19 @@
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents that the implemented classes are composite specifications.
+ ///
+ /// The type of the object to which the specification is applied.
+ public interface ICompositeSpecification : ISpecification
+ {
+ ///
+ /// Gets the left side of the specification.
+ ///
+ ISpecification Left { get; }
+
+ ///
+ /// Gets the right side of the specification.
+ ///
+ ISpecification Right { get; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs
new file mode 100644
index 0000000000..d19e28347e
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents that the implemented classes are specifications. For more
+ /// information about the specification pattern, please refer to
+ /// http://martinfowler.com/apsupp/spec.pdf.
+ ///
+ /// The type of the object to which the specification is applied.
+ public interface ISpecification
+ {
+ ///
+ /// Returns a value which indicates whether the specification
+ /// is satisfied by the given object.
+ ///
+ /// The object to which the specification is applied.
+ /// True if the specification is satisfied, otherwise false.
+ bool IsSatisfiedBy(T obj);
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ Expression> ToExpression();
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs
new file mode 100644
index 0000000000..d2c61034e0
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs
@@ -0,0 +1,19 @@
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents that the implemented classes are specification parsers that
+ /// parses the given specification to a domain specific criteria object, such
+ /// as the ICriteria instance in NHibernate.
+ ///
+ /// The type of the domain specific criteria.
+ public interface ISpecificationParser
+ {
+ ///
+ /// Parses the given specification to a domain specific criteria object.
+ ///
+ /// The type of the object to which the specification is applied.
+ /// The specified specification instance.
+ /// The instance of the domain specific criteria.
+ TCriteria Parse(ISpecification specification);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs
new file mode 100644
index 0000000000..fc2e74fdd6
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification that can be satisfied by the given object
+ /// in no circumstance.
+ ///
+ /// The type of the object to which the specification is applied.
+ public sealed class NoneSpecification : Specification
+ {
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return o => false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs
new file mode 100644
index 0000000000..34658885cb
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification which indicates the semantics opposite to the given specification.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class NotSpecification : Specification
+ {
+ private readonly ISpecification _specification;
+
+ ///
+ /// Initializes a new instance of class.
+ ///
+ /// The specification to be reversed.
+ public NotSpecification(ISpecification specification)
+ {
+ _specification = specification;
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ var expression = _specification.ToExpression();
+ return Expression.Lambda>(
+ Expression.Not(expression.Body),
+ expression.Parameters
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs
new file mode 100644
index 0000000000..a6c85f8326
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the combined specification which indicates that either of the given
+ /// specification should be satisfied by the given object.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class OrSpecification : CompositeSpecification
+ {
+ ///
+ /// Initializes a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ public OrSpecification(ISpecification left, ISpecification right) : base(left, right)
+ {
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return Left.ToExpression().Or(Right.ToExpression());
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs
new file mode 100644
index 0000000000..603e80cf69
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the parameter rebinder used for rebinding the parameters
+ /// for the given expressions. This is part of the solution which solves
+ /// the expression parameter problem when going to Entity Framework.
+ /// For more information about this solution please refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx.
+ ///
+ internal class ParameterRebinder : ExpressionVisitor
+ {
+ private readonly Dictionary _map;
+
+ internal ParameterRebinder(Dictionary map)
+ {
+ _map = map ?? new Dictionary();
+ }
+
+ internal static Expression ReplaceParameters(Dictionary map,
+ Expression exp)
+ {
+ return new ParameterRebinder(map).Visit(exp);
+ }
+
+ protected override Expression VisitParameter(ParameterExpression p)
+ {
+ if (_map.TryGetValue(p, out var replacement))
+ {
+ p = replacement;
+ }
+
+ return base.VisitParameter(p);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs
new file mode 100644
index 0000000000..b1ab72caea
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the base class for specifications.
+ ///
+ /// The type of the object to which the specification is applied.
+ public abstract class Specification : ISpecification
+ {
+ ///
+ /// Returns a value which indicates whether the specification
+ /// is satisfied by the given object.
+ ///
+ /// The object to which the specification is applied.
+ /// True if the specification is satisfied, otherwise false.
+ public virtual bool IsSatisfiedBy(T obj)
+ {
+ return ToExpression().Compile()(obj);
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public abstract Expression> ToExpression();
+
+ ///
+ /// Implicitly converts a specification to expression.
+ ///
+ ///
+ public static implicit operator Expression>(Specification specification)
+ {
+ return specification.ToExpression();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs
new file mode 100644
index 0000000000..229b3e7a72
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs
@@ -0,0 +1,72 @@
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Specifications
+{
+ public static class SpecificationExtensions
+ {
+ ///
+ /// Combines the current specification instance with another specification instance
+ /// and returns the combined specification which represents that both the current and
+ /// the given specification must be satisfied by the given object.
+ ///
+ /// The specification
+ /// The specification instance with which the current specification is combined.
+ /// The combined specification instance.
+ public static ISpecification And([NotNull] this ISpecification specification,
+ [NotNull] ISpecification other)
+ {
+ Check.NotNull(specification, nameof(specification));
+ Check.NotNull(other, nameof(other));
+
+ return new AndSpecification(specification, other);
+ }
+
+ ///
+ /// Combines the current specification instance with another specification instance
+ /// and returns the combined specification which represents that either the current or
+ /// the given specification should be satisfied by the given object.
+ ///
+ /// The specification
+ /// The specification instance with which the current specification
+ /// is combined.
+ /// The combined specification instance.
+ public static ISpecification Or([NotNull] this ISpecification specification,
+ [NotNull] ISpecification other)
+ {
+ Check.NotNull(specification, nameof(specification));
+ Check.NotNull(other, nameof(other));
+
+ return new OrSpecification(specification, other);
+ }
+
+ ///
+ /// Combines the current specification instance with another specification instance
+ /// and returns the combined specification which represents that the current specification
+ /// should be satisfied by the given object but the specified specification should not.
+ ///
+ /// The specification
+ /// The specification instance with which the current specification
+ /// is combined.
+ /// The combined specification instance.
+ public static ISpecification AndNot([NotNull] this ISpecification specification,
+ [NotNull] ISpecification other)
+ {
+ Check.NotNull(specification, nameof(specification));
+ Check.NotNull(other, nameof(other));
+
+ return new AndNotSpecification(specification, other);
+ }
+
+ ///
+ /// Reverses the current specification instance and returns a specification which represents
+ /// the semantics opposite to the current specification.
+ ///
+ /// The reversed specification instance.
+ public static ISpecification Not([NotNull] this ISpecification specification)
+ {
+ Check.NotNull(specification, nameof(specification));
+
+ return new NotSpecification(specification);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Specifications_Tests.cs
new file mode 100644
index 0000000000..d9de34f1ce
--- /dev/null
+++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Specifications_Tests.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.TestApp.Testing;
+
+namespace Volo.Abp.EntityFrameworkCore.Repositories
+{
+ public class Repository_Specifications_Tests : Repository_Specifications_Tests
+ {
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Repositories/Repository_Specifications_Tests.cs
new file mode 100644
index 0000000000..e3183127a0
--- /dev/null
+++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Repositories/Repository_Specifications_Tests.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.TestApp.Testing;
+
+namespace Volo.Abp.MemoryDb.Repositories
+{
+ public class Repository_Specifications_Tests : Repository_Specifications_Tests
+ {
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs
new file mode 100644
index 0000000000..6529b4c5a9
--- /dev/null
+++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.TestApp.Testing;
+
+namespace Volo.Abp.MongoDB.Repositories
+{
+ public class Repository_Specifications_Tests : Repository_Specifications_Tests
+ {
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj b/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj
new file mode 100644
index 0000000000..6f2cbe09f8
--- /dev/null
+++ b/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp2.2
+ latest
+ Volo.Abp.Specifications.Tests
+ Volo.Abp.Specifications.Tests
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs b/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs
new file mode 100644
index 0000000000..dbe4fc6b5d
--- /dev/null
+++ b/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using Shouldly;
+using Xunit;
+
+namespace Volo.Abp.Specifications
+{
+ public class Specification_Tests
+ {
+ private readonly IQueryable _customers;
+
+ public Specification_Tests()
+ {
+ _customers = new List
+ {
+ new Customer("John", 17, 47000, "England"),
+ new Customer("Tuana", 2, 500, "Turkey"),
+ new Customer("Martin", 43, 16000, "USA"),
+ new Customer("Lee", 32, 24502, "China"),
+ new Customer("Douglas", 42, 42000, "England"),
+ new Customer("Abelard", 14, 2332, "German"),
+ new Customer("Neo", 16, 120000, "USA"),
+ new Customer("Daan", 39, 6000, "Netherlands"),
+ new Customer("Alessandro", 22, 8271, "Italy"),
+ new Customer("Noah", 33, 82192, "Belgium")
+ }.AsQueryable();
+ }
+
+ [Fact]
+ public void Any_Should_Return_All()
+ {
+ _customers
+ .Where(new AnySpecification()) //Implicitly converted to Expression!
+ .Count()
+ .ShouldBe(_customers.Count());
+ }
+
+ [Fact]
+ public void None_Should_Return_Empty()
+ {
+ _customers
+ .Where(new NoneSpecification().ToExpression())
+ .Count()
+ .ShouldBe(0);
+ }
+
+ [Fact]
+ public void Not_Should_Return_Reverse_Result()
+ {
+ _customers
+ .Where(new EuropeanCustomerSpecification().Not().ToExpression())
+ .Count()
+ .ShouldBe(3);
+ }
+
+ [Fact]
+ public void Should_Support_Native_Expressions_And_Combinations()
+ {
+ _customers
+ .Where(new ExpressionSpecification(c => c.Age >= 18).ToExpression())
+ .Count()
+ .ShouldBe(6);
+
+ _customers
+ .Where(new EuropeanCustomerSpecification().And(new ExpressionSpecification(c => c.Age >= 18)).ToExpression())
+ .Count()
+ .ShouldBe(4);
+ }
+
+ [Fact]
+ public void CustomSpecification_Test()
+ {
+ _customers
+ .Where(new EuropeanCustomerSpecification().ToExpression())
+ .Count()
+ .ShouldBe(7);
+
+ _customers
+ .Where(new Age18PlusCustomerSpecification().ToExpression())
+ .Count()
+ .ShouldBe(6);
+
+ _customers
+ .Where(new BalanceCustomerSpecification(10000, 30000).ToExpression())
+ .Count()
+ .ShouldBe(2);
+
+ _customers
+ .Where(new PremiumCustomerSpecification().ToExpression())
+ .Count()
+ .ShouldBe(3);
+ }
+
+ [Fact]
+ public void IsSatisfiedBy_Tests()
+ {
+ new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 49, 55000, "USA")).ShouldBeTrue();
+
+ new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 49, 200, "USA")).ShouldBeFalse();
+ new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 12, 55000, "USA")).ShouldBeFalse();
+ }
+
+ [Fact]
+ public void CustomSpecification_Composite_Tests()
+ {
+ _customers
+ .Where(new EuropeanCustomerSpecification().And(new Age18PlusCustomerSpecification()).ToExpression())
+ .Count()
+ .ShouldBe(4);
+
+ _customers
+ .Where(new EuropeanCustomerSpecification().Not().And(new Age18PlusCustomerSpecification()).ToExpression())
+ .Count()
+ .ShouldBe(2);
+
+ _customers
+ .Where(new Age18PlusCustomerSpecification().AndNot(new EuropeanCustomerSpecification()).ToExpression())
+ .Count()
+ .ShouldBe(2);
+ }
+
+ private class Customer
+ {
+ public string Name { get; private set; }
+
+ public byte Age { get; private set; }
+
+ public long Balance { get; private set; }
+
+ public string Location { get; private set; }
+
+ public Customer(string name, byte age, long balance, string location)
+ {
+ Name = name;
+ Age = age;
+ Balance = balance;
+ Location = location;
+ }
+ }
+
+ private class EuropeanCustomerSpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return c => c.Location == "England" ||
+ c.Location == "Turkey" ||
+ c.Location == "German" ||
+ c.Location == "Netherlands" ||
+ c.Location == "Italy" ||
+ c.Location == "Belgium";
+ }
+ }
+
+ private class Age18PlusCustomerSpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return c => c.Age >= 18;
+ }
+ }
+
+ private class BalanceCustomerSpecification : Specification
+ {
+ public int MinBalance { get; }
+
+ public int MaxBalance { get; }
+
+ public BalanceCustomerSpecification(int minBalance, int maxBalance)
+ {
+ MinBalance = minBalance;
+ MaxBalance = maxBalance;
+ }
+
+ public override Expression> ToExpression()
+ {
+ return c => c.Balance >= MinBalance && c.Balance <= MaxBalance;
+ }
+ }
+
+ private class PremiumCustomerSpecification : AndSpecification
+ {
+ public PremiumCustomerSpecification()
+ : base(new Age18PlusCustomerSpecification(), new BalanceCustomerSpecification(20000, int.MaxValue))
+ {
+ }
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj
index 0cb921f63a..4e7b58ee84 100644
--- a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj
+++ b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj
@@ -16,6 +16,7 @@
+
diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Specifications_Tests.cs
new file mode 100644
index 0000000000..a3e7907135
--- /dev/null
+++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Specifications_Tests.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using Shouldly;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Modularity;
+using Volo.Abp.Specifications;
+using Volo.Abp.TestApp.Domain;
+using Xunit;
+
+namespace Volo.Abp.TestApp.Testing
+{
+ public abstract class Repository_Specifications_Tests : TestAppTestBase
+ where TStartupModule : IAbpModule
+ {
+ protected readonly IRepository CityRepository;
+
+ protected Repository_Specifications_Tests()
+ {
+ CityRepository = GetRequiredService>();
+ }
+
+ [Fact]
+ public void SpecificationWithRepository_Test()
+ {
+ WithUnitOfWork(() =>
+ {
+ CityRepository.Count(new CitySpecification().ToExpression()).ShouldBe(1);
+ });
+ }
+ }
+
+ public class CitySpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return city => city.Name == "Istanbul";
+ }
+ }
+}
\ No newline at end of file
diff --git a/nupkg/common.ps1 b/nupkg/common.ps1
index 5f6944b1a6..59329e6af0 100644
--- a/nupkg/common.ps1
+++ b/nupkg/common.ps1
@@ -88,6 +88,7 @@ $projects = (
"framework/src/Volo.Abp.Serialization",
"framework/src/Volo.Abp.Settings",
"framework/src/Volo.Abp.Sms",
+ "framework/src/Volo.Abp.Specifications",
"framework/src/Volo.Abp.TestBase",
"framework/src/Volo.Abp.Threading",
"framework/src/Volo.Abp.Timing",
diff --git a/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
index 330cf78fe2..c06daeed28 100644
--- a/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
+++ b/templates/mvc/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
@@ -32,10 +32,9 @@ using Volo.Abp.UI;
using Volo.Abp.UI.Navigation;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.PermissionManagement;
+using Volo.Abp.TenantManagement.Web;
//
using Volo.Abp.EntityFrameworkCore;
-using Volo.Abp.TenantManagement.Web;
-
//
namespace MyCompanyName.MyProjectName