mirror of https://github.com/abpframework/abp.git
41 changed files with 1114 additions and 30 deletions
@ -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 + ";"; |
|||
} |
|||
} |
|||
} |
|||
@ -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<T></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)) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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"; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue