mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-22 05:22:57 +01:00
SievePropertyMapper for #4
This commit is contained in:
parent
b52362e2bc
commit
0bd38b8348
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Sieve.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class SieveAttribute : Attribute
|
||||
public class SieveAttribute : Attribute, ISievePropertyMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Override name used
|
||||
|
14
Sieve/Models/ISievePropertyMetadata.cs
Normal file
14
Sieve/Models/ISievePropertyMetadata.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public interface ISievePropertyMetadata
|
||||
{
|
||||
string Name { get; set; }
|
||||
bool CanFilter { get; set; }
|
||||
bool CanSort { get; set; }
|
||||
}
|
||||
}
|
14
Sieve/Models/SievePropertyMetadata.cs
Normal file
14
Sieve/Models/SievePropertyMetadata.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public class SievePropertyMetadata : ISievePropertyMetadata
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool CanFilter { get; set; }
|
||||
public bool CanSort { get; set; }
|
||||
}
|
||||
}
|
@ -19,12 +19,14 @@ namespace Sieve.Services
|
||||
private IOptions<SieveOptions> _options;
|
||||
private ISieveCustomSortMethods _customSortMethods;
|
||||
private ISieveCustomFilterMethods _customFilterMethods;
|
||||
private SievePropertyMapper mapper = new SievePropertyMapper();
|
||||
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomSortMethods customSortMethods,
|
||||
ISieveCustomFilterMethods customFilterMethods)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_options = options;
|
||||
_customSortMethods = customSortMethods;
|
||||
_customFilterMethods = customFilterMethods;
|
||||
@ -33,6 +35,7 @@ namespace Sieve.Services
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomSortMethods customSortMethods)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_options = options;
|
||||
_customSortMethods = customSortMethods;
|
||||
}
|
||||
@ -40,12 +43,14 @@ namespace Sieve.Services
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomFilterMethods customFilterMethods)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_options = options;
|
||||
_customFilterMethods = customFilterMethods;
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_options = options;
|
||||
}
|
||||
|
||||
@ -57,7 +62,10 @@ namespace Sieve.Services
|
||||
/// <param name="source">Data source</param>
|
||||
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
|
||||
/// <returns>Returns a transformed version of `source`</returns>
|
||||
public IQueryable<TEntity> ApplyAll<TEntity>(ISieveModel<IFilterTerm, ISortTerm> model, IQueryable<TEntity> source, object[] dataForCustomMethods = null)
|
||||
public IQueryable<TEntity> ApplyAll<TEntity>(
|
||||
ISieveModel<IFilterTerm, ISortTerm> model,
|
||||
IQueryable<TEntity> source,
|
||||
object[] dataForCustomMethods = null)
|
||||
{
|
||||
var result = source;
|
||||
|
||||
@ -84,7 +92,10 @@ namespace Sieve.Services
|
||||
/// <param name="source">Data source</param>
|
||||
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
|
||||
/// <returns>Returns a transformed version of `source`</returns>
|
||||
public IQueryable<TEntity> ApplyFiltering<TEntity>(ISieveModel<IFilterTerm, ISortTerm> model, IQueryable<TEntity> result, object[] dataForCustomMethods = null)
|
||||
public IQueryable<TEntity> ApplyFiltering<TEntity>(
|
||||
ISieveModel<IFilterTerm, ISortTerm> model,
|
||||
IQueryable<TEntity> result,
|
||||
object[] dataForCustomMethods = null)
|
||||
{
|
||||
if (model?.FiltersParsed == null)
|
||||
return result;
|
||||
@ -181,7 +192,10 @@ namespace Sieve.Services
|
||||
/// <param name="source">Data source</param>
|
||||
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
|
||||
/// <returns>Returns a transformed version of `source`</returns>
|
||||
public IQueryable<TEntity> ApplySorting<TEntity>(ISieveModel<IFilterTerm, ISortTerm> model, IQueryable<TEntity> result, object[] dataForCustomMethods = null)
|
||||
public IQueryable<TEntity> ApplySorting<TEntity>(
|
||||
ISieveModel<IFilterTerm, ISortTerm> model,
|
||||
IQueryable<TEntity> result,
|
||||
object[] dataForCustomMethods = null)
|
||||
{
|
||||
if (model?.SortsParsed == null)
|
||||
return result;
|
||||
@ -219,7 +233,9 @@ namespace Sieve.Services
|
||||
/// <param name="source">Data source</param>
|
||||
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
|
||||
/// <returns>Returns a transformed version of `source`</returns>
|
||||
public IQueryable<TEntity> ApplyPagination<TEntity>(ISieveModel<IFilterTerm, ISortTerm> model, IQueryable<TEntity> result)
|
||||
public IQueryable<TEntity> ApplyPagination<TEntity>(
|
||||
ISieveModel<IFilterTerm, ISortTerm> model,
|
||||
IQueryable<TEntity> result)
|
||||
{
|
||||
var page = model?.Page ?? 1;
|
||||
var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize;
|
||||
@ -232,15 +248,34 @@ namespace Sieve.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
private PropertyInfo GetSieveProperty<TEntity>(bool canSortRequired, bool canFilterRequired, string name)
|
||||
|
||||
protected virtual SievePropertyMapper MapProperties(SievePropertyMapper mapper)
|
||||
{
|
||||
return mapper;
|
||||
}
|
||||
|
||||
private PropertyInfo GetSieveProperty<TEntity>(
|
||||
bool canSortRequired,
|
||||
bool canFilterRequired,
|
||||
string name)
|
||||
{
|
||||
return mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive)
|
||||
?? FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
|
||||
}
|
||||
|
||||
private PropertyInfo FindPropertyBySieveAttribute<TEntity>(
|
||||
bool canSortRequired,
|
||||
bool canFilterRequired,
|
||||
string name,
|
||||
bool isCaseSensitive)
|
||||
{
|
||||
return typeof(TEntity).GetProperties().FirstOrDefault(p =>
|
||||
{
|
||||
if (p.GetCustomAttribute(typeof(SieveAttribute)) is SieveAttribute sieveAttribute)
|
||||
if ((canSortRequired ? sieveAttribute.CanSort : true) &&
|
||||
(canFilterRequired ? sieveAttribute.CanFilter : true) &&
|
||||
((sieveAttribute.Name ?? p.Name).Equals(name,
|
||||
_options.Value.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)))
|
||||
((sieveAttribute.Name ?? p.Name).Equals(name,
|
||||
isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
107
Sieve/Services/SievePropertyMapper.cs
Normal file
107
Sieve/Services/SievePropertyMapper.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using Sieve.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Services
|
||||
{
|
||||
public class SievePropertyMapper
|
||||
{
|
||||
private Dictionary<Type, Dictionary<PropertyInfo, ISievePropertyMetadata>> _map
|
||||
= new Dictionary<Type, Dictionary<PropertyInfo, ISievePropertyMetadata>>();
|
||||
|
||||
public PropertyFluentApi<TEntity> Property<TEntity>(Expression<Func<TEntity, object>> expression)
|
||||
{
|
||||
_map.TryAdd(typeof(TEntity), new Dictionary<PropertyInfo, ISievePropertyMetadata>());
|
||||
return new PropertyFluentApi<TEntity>(this, expression);
|
||||
}
|
||||
|
||||
public class PropertyFluentApi<TEntity>
|
||||
{
|
||||
private SievePropertyMapper _sievePropertyMapper;
|
||||
private PropertyInfo _property;
|
||||
|
||||
public PropertyFluentApi(SievePropertyMapper sievePropertyMapper, Expression<Func<TEntity, object>> expression)
|
||||
{
|
||||
_sievePropertyMapper = sievePropertyMapper;
|
||||
_property = GetPropertyInfo(expression);
|
||||
_name = _property.Name;
|
||||
_canFilter = false;
|
||||
_canSort = false;
|
||||
}
|
||||
|
||||
private string _name;
|
||||
private bool _canFilter;
|
||||
private bool _canSort;
|
||||
|
||||
public PropertyFluentApi<TEntity> CanFilter()
|
||||
{
|
||||
_canFilter = true;
|
||||
UpdateMap();
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyFluentApi<TEntity> CanSort()
|
||||
{
|
||||
_canSort = true;
|
||||
UpdateMap();
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyFluentApi<TEntity> HasName(string name)
|
||||
{
|
||||
_name = name;
|
||||
UpdateMap();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void UpdateMap()
|
||||
{
|
||||
_sievePropertyMapper._map[typeof(TEntity)][_property] = new SievePropertyMetadata()
|
||||
{
|
||||
Name = _name,
|
||||
CanFilter = _canFilter,
|
||||
CanSort = _canSort
|
||||
};
|
||||
}
|
||||
|
||||
private static PropertyInfo GetPropertyInfo(Expression<Func<TEntity, object>> exp)
|
||||
{
|
||||
if (!(exp.Body is MemberExpression body))
|
||||
{
|
||||
var ubody = (UnaryExpression)exp.Body;
|
||||
body = ubody.Operand as MemberExpression;
|
||||
}
|
||||
|
||||
return body?.Member as PropertyInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public PropertyInfo FindProperty<TEntity>(
|
||||
bool canSortRequired,
|
||||
bool canFilterRequired,
|
||||
string name,
|
||||
bool isCaseSensitive)
|
||||
{
|
||||
try
|
||||
{
|
||||
var me = _map[typeof(TEntity)]
|
||||
.FirstOrDefault(kv =>
|
||||
kv.Value.Name.Equals(name, isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) &&
|
||||
(canSortRequired ? kv.Value.CanSort : true) &&
|
||||
(canFilterRequired ? kv.Value.CanFilter : true));
|
||||
|
||||
return me.Key;
|
||||
}
|
||||
catch (Exception ex) when (ex is KeyNotFoundException || ex is ArgumentNullException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
28
SieveTests/Services/ApplicationSieveProcessor.cs
Normal file
28
SieveTests/Services/ApplicationSieveProcessor.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveTests.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SieveTests.Services
|
||||
{
|
||||
public class ApplicationSieveProcessor : SieveProcessor
|
||||
{
|
||||
public ApplicationSieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
|
||||
{
|
||||
mapper.Property<Post>(p => p.Title)
|
||||
.CanSort()
|
||||
.CanFilter()
|
||||
.HasName("CustomTitleName");
|
||||
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ namespace SieveTests
|
||||
|
||||
services.AddScoped<ISieveCustomSortMethods, SieveCustomSortMethods>();
|
||||
services.AddScoped<ISieveCustomFilterMethods, SieveCustomFilterMethods>();
|
||||
services.AddScoped<ISieveProcessor, SieveProcessor>();
|
||||
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
@ -21,5 +21,11 @@ namespace SieveUnitTests.Entities
|
||||
|
||||
[Sieve(CanFilter = true, CanSort = true)]
|
||||
public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;
|
||||
|
||||
public string ThisHasNoAttribute { get; set; }
|
||||
|
||||
public string ThisHasNoAttributeButIsAccessible { get; set; }
|
||||
|
||||
public int OnlySortableViaFluentApi { get; set; }
|
||||
}
|
||||
}
|
||||
|
84
SieveUnitTests/Mapper.cs
Normal file
84
SieveUnitTests/Mapper.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveUnitTests.Entities;
|
||||
using SieveUnitTests.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SieveUnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class Mapper
|
||||
{
|
||||
private ApplicationSieveProcessor _processor;
|
||||
private IQueryable<Post> _posts;
|
||||
|
||||
public Mapper()
|
||||
{
|
||||
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||
new SieveCustomSortMethods(),
|
||||
new SieveCustomFilterMethods());
|
||||
|
||||
_posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
Id = 1,
|
||||
ThisHasNoAttributeButIsAccessible = "A",
|
||||
ThisHasNoAttribute = "A",
|
||||
OnlySortableViaFluentApi = 100
|
||||
},
|
||||
new Post() {
|
||||
Id = 2,
|
||||
ThisHasNoAttributeButIsAccessible = "B",
|
||||
ThisHasNoAttribute = "B",
|
||||
OnlySortableViaFluentApi = 50
|
||||
},
|
||||
new Post() {
|
||||
Id = 3,
|
||||
ThisHasNoAttributeButIsAccessible = "C",
|
||||
ThisHasNoAttribute = "C",
|
||||
OnlySortableViaFluentApi = 0
|
||||
},
|
||||
}.AsQueryable();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapperWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
{
|
||||
Filters = "shortname@=A",
|
||||
};
|
||||
|
||||
var result = _processor.ApplyAll(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().ThisHasNoAttributeButIsAccessible, "A");
|
||||
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MapperSortOnlyWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
{
|
||||
Filters = "OnlySortableViaFluentApi@=50",
|
||||
Sorts = "OnlySortableViaFluentApi"
|
||||
};
|
||||
|
||||
var result = _processor.ApplyAll(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 3);
|
||||
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//Sorts = "LikeCount",
|
||||
//Page = 1,
|
||||
//PageSize = 10
|
||||
//
|
31
SieveUnitTests/Services/ApplicationSieveProcessor.cs
Normal file
31
SieveUnitTests/Services/ApplicationSieveProcessor.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveUnitTests.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SieveUnitTests.Services
|
||||
{
|
||||
public class ApplicationSieveProcessor : SieveProcessor
|
||||
{
|
||||
public ApplicationSieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
|
||||
{
|
||||
mapper.Property<Post>(p => p.ThisHasNoAttributeButIsAccessible)
|
||||
.CanSort()
|
||||
.CanFilter()
|
||||
.HasName("shortname");
|
||||
|
||||
mapper.Property<Post>(p => p.OnlySortableViaFluentApi)
|
||||
.CanSort();
|
||||
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user