pull/1466/head^2
Yunus Emre Kalkan 7 years ago
commit a82451ee7b

@ -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;
}

@ -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}

@ -17,6 +17,7 @@
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
<PackageReference Include="System.Security.Permissions" Version="4.5.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" />
</ItemGroup>
<ItemGroup>

@ -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);
});

@ -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<CliService> Logger { get; set; }

@ -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<AddCommand> Logger { get; set; }
public ILogger<AddModuleCommand> Logger { get; set; }
protected ModuleAdder ModuleAdder { get; }
public AddCommand(ModuleAdder moduleAdder)
public AddModuleCommand(ModuleAdder moduleAdder)
{
ModuleAdder = moduleAdder;
Logger = NullLogger<AddCommand>.Instance;
Logger = NullLogger<AddModuleCommand>.Instance;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)

@ -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;
}

@ -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<string> Find(string csprojFilePath)
{
var modules = new List<string>();
var csFiles = GetAllCsFilesUnderDirectory(Path.GetDirectoryName(csprojFilePath), new List<string>());
foreach (var csFile in csFiles)
{
try
{
var root = CSharpSyntaxTree.ParseText(File.ReadAllText(csFile)).GetRoot();
var namespaceSyntaxs = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>();
foreach (var namespaceSyntax in namespaceSyntaxs)
{
var classDeclarationSyntaxs = namespaceSyntax.DescendantNodes().OfType<ClassDeclarationSyntax>();
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<string> GetAllCsFilesUnderDirectory(string path, List<string> 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;
}
}
}

@ -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 + ";";
}
}
}

@ -15,12 +15,12 @@ namespace Volo.Abp.ProjectModification
{
public ILogger<ModuleAdder> 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<ModuleAdder>.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())

@ -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; }
}
}

@ -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; }
}
}

@ -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)
{
}
}
}

@ -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<ProjectNpmPackageAdder> Logger { get; set; }
public ProjectNpmPackageAdder()
{
Logger = NullLogger<ProjectNpmPackageAdder>.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;
}
}
}

@ -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<ProjectNugetPackageAdder> Logger { get; set; }
protected IJsonSerializer JsonSerializer { get; }
protected ProjectNpmPackageAdder NpmPackageAdder { get; }
public ProjectNugetPackageAdder(
IJsonSerializer jsonSerializer,
ProjectNpmPackageAdder npmPackageAdder)
{
JsonSerializer = jsonSerializer;
NpmPackageAdder = npmPackageAdder;
Logger = NullLogger<ProjectNugetPackageAdder>.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<NugetPackageInfo> 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<NugetPackageInfo>(responseContent);
}
}
}
}

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.Specifications</AssemblyName>
<PackageId>Volo.Abp.Specifications</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Specifications
{
public class AbpSpecificationsModule : AbpModule
{
}
}

@ -0,0 +1,36 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the combined specification which indicates that the first specification
/// can be satisifed by the given object whereas the second one cannot.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class AndNotSpecification<T> : CompositeSpecification<T>
{
/// <summary>
/// Constructs a new instance of <see cref="AndNotSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
public AndNotSpecification(ISpecification<T> left, ISpecification<T> right) : base(left, right)
{
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
var rightExpression = Right.ToExpression();
var bodyNot = Expression.Not(rightExpression.Body);
var bodyNotExpression = Expression.Lambda<Func<T, bool>>(bodyNot, rightExpression.Parameters);
return Left.ToExpression().And(bodyNotExpression);
}
}
}

@ -0,0 +1,31 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the combined specification which indicates that both of the given
/// specifications should be satisfied by the given object.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class AndSpecification<T> : CompositeSpecification<T>
{
/// <summary>
/// Constructs a new instance of <see cref="AndSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
public AndSpecification(ISpecification<T> left, ISpecification<T> right) : base(left, right)
{
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return Left.ToExpression().And(Right.ToExpression());
}
}
}

@ -0,0 +1,22 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification that can be satisfied by the given object
/// in any circumstance.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public sealed class AnySpecification<T> : Specification<T>
{
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return o => true;
}
}
}

@ -0,0 +1,30 @@
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the base class for composite specifications.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public abstract class CompositeSpecification<T> : Specification<T>, ICompositeSpecification<T>
{
/// <summary>
/// Constructs a new instance of <see cref="CompositeSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
protected CompositeSpecification(ISpecification<T> left, ISpecification<T> right)
{
Left = left;
Right = right;
}
/// <summary>
/// Gets the first specification.
/// </summary>
public ISpecification<T> Left { get; }
/// <summary>
/// Gets the second specification.
/// </summary>
public ISpecification<T> Right { get; }
}
}

@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// 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.
/// </summary>
public static class ExpressionFuncExtender
{
private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second,
Func<Expression, Expression, Expression> 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<T>(merge(first.Body, secondBody), first.Parameters);
}
/// <summary>
/// Combines two given expressions by using the AND semantics.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="first">The first part of the expression.</param>
/// <param name="second">The second part of the expression.</param>
/// <returns>The combined expression.</returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines two given expressions by using the OR semantics.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="first">The first part of the expression.</param>
/// <param name="second">The second part of the expression.</param>
/// <returns>The combined expression.</returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
}
}

@ -0,0 +1,34 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification which is represented by the corresponding
/// LINQ expression.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class ExpressionSpecification<T> : Specification<T>
{
private readonly Expression<Func<T, bool>> _expression;
/// <summary>
/// Initializes a new instance of <c>ExpressionSpecification&lt;T&gt;</c> class.
/// </summary>
/// <param name="expression">The LINQ expression which represents the current
/// specification.</param>
public ExpressionSpecification(Expression<Func<T, bool>> expression)
{
_expression = expression;
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return _expression;
}
}
}

@ -0,0 +1,19 @@
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents that the implemented classes are composite specifications.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public interface ICompositeSpecification<T> : ISpecification<T>
{
/// <summary>
/// Gets the left side of the specification.
/// </summary>
ISpecification<T> Left { get; }
/// <summary>
/// Gets the right side of the specification.
/// </summary>
ISpecification<T> Right { get; }
}
}

@ -0,0 +1,28 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents that the implemented classes are specifications. For more
/// information about the specification pattern, please refer to
/// http://martinfowler.com/apsupp/spec.pdf.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public interface ISpecification<T>
{
/// <summary>
/// Returns a <see cref="bool"/> value which indicates whether the specification
/// is satisfied by the given object.
/// </summary>
/// <param name="obj">The object to which the specification is applied.</param>
/// <returns>True if the specification is satisfied, otherwise false.</returns>
bool IsSatisfiedBy(T obj);
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
Expression<Func<T, bool>> ToExpression();
}
}

@ -0,0 +1,19 @@
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents that the implemented classes are specification parsers that
/// parses the given specification to a domain specific criteria object, such
/// as the <c>ICriteria</c> instance in NHibernate.
/// </summary>
/// <typeparam name="TCriteria">The type of the domain specific criteria.</typeparam>
public interface ISpecificationParser<out TCriteria>
{
/// <summary>
/// Parses the given specification to a domain specific criteria object.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
/// <param name="specification">The specified specification instance.</param>
/// <returns>The instance of the domain specific criteria.</returns>
TCriteria Parse<T>(ISpecification<T> specification);
}
}

@ -0,0 +1,22 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification that can be satisfied by the given object
/// in no circumstance.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public sealed class NoneSpecification<T> : Specification<T>
{
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return o => false;
}
}
}

@ -0,0 +1,36 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification which indicates the semantics opposite to the given specification.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class NotSpecification<T> : Specification<T>
{
private readonly ISpecification<T> _specification;
/// <summary>
/// Initializes a new instance of <see cref="NotSpecification{T}"/> class.
/// </summary>
/// <param name="specification">The specification to be reversed.</param>
public NotSpecification(ISpecification<T> specification)
{
_specification = specification;
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
var expression = _specification.ToExpression();
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expression.Body),
expression.Parameters
);
}
}
}

@ -0,0 +1,31 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the combined specification which indicates that either of the given
/// specification should be satisfied by the given object.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class OrSpecification<T> : CompositeSpecification<T>
{
/// <summary>
/// Initializes a new instance of <see cref="OrSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
public OrSpecification(ISpecification<T> left, ISpecification<T> right) : base(left, right)
{
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return Left.ToExpression().Or(Right.ToExpression());
}
}
}

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// 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.
/// </summary>
internal class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> _map;
internal ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
_map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> 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);
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the base class for specifications.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public abstract class Specification<T> : ISpecification<T>
{
/// <summary>
/// Returns a <see cref="bool"/> value which indicates whether the specification
/// is satisfied by the given object.
/// </summary>
/// <param name="obj">The object to which the specification is applied.</param>
/// <returns>True if the specification is satisfied, otherwise false.</returns>
public virtual bool IsSatisfiedBy(T obj)
{
return ToExpression().Compile()(obj);
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public abstract Expression<Func<T, bool>> ToExpression();
/// <summary>
/// Implicitly converts a specification to expression.
/// </summary>
/// <param name="specification"></param>
public static implicit operator Expression<Func<T, bool>>(Specification<T> specification)
{
return specification.ToExpression();
}
}
}

@ -0,0 +1,72 @@
using JetBrains.Annotations;
namespace Volo.Abp.Specifications
{
public static class SpecificationExtensions
{
/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The specification</param>
/// <param name="other">The specification instance with which the current specification is combined.</param>
/// <returns>The combined specification instance.</returns>
public static ISpecification<T> And<T>([NotNull] this ISpecification<T> specification,
[NotNull] ISpecification<T> other)
{
Check.NotNull(specification, nameof(specification));
Check.NotNull(other, nameof(other));
return new AndSpecification<T>(specification, other);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The specification</param>
/// <param name="other">The specification instance with which the current specification
/// is combined.</param>
/// <returns>The combined specification instance.</returns>
public static ISpecification<T> Or<T>([NotNull] this ISpecification<T> specification,
[NotNull] ISpecification<T> other)
{
Check.NotNull(specification, nameof(specification));
Check.NotNull(other, nameof(other));
return new OrSpecification<T>(specification, other);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The specification</param>
/// <param name="other">The specification instance with which the current specification
/// is combined.</param>
/// <returns>The combined specification instance.</returns>
public static ISpecification<T> AndNot<T>([NotNull] this ISpecification<T> specification,
[NotNull] ISpecification<T> other)
{
Check.NotNull(specification, nameof(specification));
Check.NotNull(other, nameof(other));
return new AndNotSpecification<T>(specification, other);
}
/// <summary>
/// Reverses the current specification instance and returns a specification which represents
/// the semantics opposite to the current specification.
/// </summary>
/// <returns>The reversed specification instance.</returns>
public static ISpecification<T> Not<T>([NotNull] this ISpecification<T> specification)
{
Check.NotNull(specification, nameof(specification));
return new NotSpecification<T>(specification);
}
}
}

@ -0,0 +1,8 @@
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.EntityFrameworkCore.Repositories
{
public class Repository_Specifications_Tests : Repository_Specifications_Tests<AbpEntityFrameworkCoreTestModule>
{
}
}

@ -0,0 +1,8 @@
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.MemoryDb.Repositories
{
public class Repository_Specifications_Tests : Repository_Specifications_Tests<AbpMemoryDbTestModule>
{
}
}

@ -0,0 +1,8 @@
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.MongoDB.Repositories
{
public class Repository_Specifications_Tests : Repository_Specifications_Tests<AbpMongoDbTestModule>
{
}
}

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
<AssemblyName>Volo.Abp.Specifications.Tests</AssemblyName>
<PackageId>Volo.Abp.Specifications.Tests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
</ItemGroup>
</Project>

@ -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<Customer> _customers;
public Specification_Tests()
{
_customers = new List<Customer>
{
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<Customer>()) //Implicitly converted to Expression!
.Count()
.ShouldBe(_customers.Count());
}
[Fact]
public void None_Should_Return_Empty()
{
_customers
.Where(new NoneSpecification<Customer>().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<Customer>(c => c.Age >= 18).ToExpression())
.Count()
.ShouldBe(6);
_customers
.Where(new EuropeanCustomerSpecification().And(new ExpressionSpecification<Customer>(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<Customer>
{
public override Expression<Func<Customer, bool>> 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<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Age >= 18;
}
}
private class BalanceCustomerSpecification : Specification<Customer>
{
public int MinBalance { get; }
public int MaxBalance { get; }
public BalanceCustomerSpecification(int minBalance, int maxBalance)
{
MinBalance = minBalance;
MaxBalance = maxBalance;
}
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Balance >= MinBalance && c.Balance <= MaxBalance;
}
}
private class PremiumCustomerSpecification : AndSpecification<Customer>
{
public PremiumCustomerSpecification()
: base(new Age18PlusCustomerSpecification(), new BalanceCustomerSpecification(20000, int.MaxValue))
{
}
}
}
}

@ -16,6 +16,7 @@
<ProjectReference Include="..\..\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.TestBase\Volo.Abp.TestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
</ItemGroup>

@ -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<TStartupModule> : TestAppTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly IRepository<City, Guid> CityRepository;
protected Repository_Specifications_Tests()
{
CityRepository = GetRequiredService<IRepository<City, Guid>>();
}
[Fact]
public void SpecificationWithRepository_Test()
{
WithUnitOfWork(() =>
{
CityRepository.Count(new CitySpecification().ToExpression()).ShouldBe(1);
});
}
}
public class CitySpecification : Specification<City>
{
public override Expression<Func<City, bool>> ToExpression()
{
return city => city.Name == "Istanbul";
}
}
}

@ -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",

@ -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;
//<TEMPLATE-REMOVE IF-NOT='EntityFrameworkCore'>
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.TenantManagement.Web;
//</TEMPLATE-REMOVE>
namespace MyCompanyName.MyProjectName

Loading…
Cancel
Save