Skip to content

Commit

Permalink
Merge pull request #92 from Research-Institute/develop
Browse files Browse the repository at this point in the history
v1.3.1
  • Loading branch information
jaredcnance authored Apr 13, 2017
2 parents ea3a076 + 581f4bd commit 219ccfc
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 96 deletions.
4 changes: 2 additions & 2 deletions src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace JsonApiDotNetCore.Controllers
{
public class JsonApiControllerMixin : Controller
public abstract class JsonApiControllerMixin : Controller
{
public JsonApiControllerMixin()
protected JsonApiControllerMixin()
{ }

protected IActionResult UnprocessableEntity()
Expand Down
8 changes: 4 additions & 4 deletions src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ public virtual IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQ
if(filterQuery == null)
return entities;

var attributeFilterQuery = new AttrFilterQuery(_jsonApiContext, filterQuery);

return entities
.Filter(attributeFilterQuery);
if(filterQuery.IsAttributeOfRelationship)
return entities.Filter(new RelatedAttrFilterQuery(_jsonApiContext, filterQuery));
else
return entities.Filter(new AttrFilterQuery(_jsonApiContext, filterQuery));
}

public virtual IQueryable<TEntity> Sort(IQueryable<TEntity> entities, List<SortQuery> sortQueries)
Expand Down
125 changes: 89 additions & 36 deletions src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,36 +86,7 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
// {1}
var right = Expression.Constant(convertedValue, property.PropertyType);

Expression body;
switch (filterQuery.FilterOperation)
{
case FilterOperations.eq:
// {model.Id == 1}
body = Expression.Equal(left, right);
break;
case FilterOperations.lt:
// {model.Id < 1}
body = Expression.LessThan(left, right);
break;
case FilterOperations.gt:
// {model.Id > 1}
body = Expression.GreaterThan(left, right);
break;
case FilterOperations.le:
// {model.Id <= 1}
body = Expression.LessThanOrEqual(left, right);
break;
case FilterOperations.ge:
// {model.Id <= 1}
body = Expression.GreaterThanOrEqual(left, right);
break;
case FilterOperations.like:
// {model.Id <= 1}
body = Expression.Call(left, "Contains", null, right);
break;
default:
throw new JsonApiException("500", $"Unknown filter operation {filterQuery.FilterOperation}");
}
var body = GetFilterExpressionLambda(left, right, filterQuery.FilterOperation);

var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);

Expand All @@ -126,27 +97,109 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
throw new JsonApiException("400", $"Could not cast {filterQuery.PropertyValue} to {property.PropertyType.Name}");
}
}

public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, RelatedAttrFilterQuery filterQuery)
{
if (filterQuery == null)
return source;

var concreteType = typeof(TSource);
var relation = concreteType.GetProperty(filterQuery.FilteredRelationship.InternalRelationshipName);
if (relation == null)
throw new ArgumentException($"'{filterQuery.FilteredRelationship.InternalRelationshipName}' is not a valid relationship of '{concreteType}'");

var relatedType = filterQuery.FilteredRelationship.Type;
var relatedAttr = relatedType.GetProperty(filterQuery.FilteredAttribute.InternalAttributeName);
if (relatedAttr == null)
throw new ArgumentException($"'{filterQuery.FilteredAttribute.InternalAttributeName}' is not a valid attribute of '{filterQuery.FilteredRelationship.InternalRelationshipName}'");

try
{
// convert the incoming value to the target value type
// "1" -> 1
var convertedValue = TypeHelper.ConvertType(filterQuery.PropertyValue, relatedAttr.PropertyType);
// {model}
var parameter = Expression.Parameter(concreteType, "model");

// {model.Relationship}
var leftRelationship = Expression.PropertyOrField(parameter, relation.Name);

// {model.Relationship.Attr}
var left = Expression.PropertyOrField(leftRelationship, relatedAttr.Name);

// {1}
var right = Expression.Constant(convertedValue, relatedAttr.PropertyType);

var body = GetFilterExpressionLambda(left, right, filterQuery.FilterOperation);

var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);

return source.Where(lambda);
}
catch (FormatException)
{
throw new JsonApiException("400", $"Could not cast {filterQuery.PropertyValue} to {relatedAttr.PropertyType.Name}");
}
}

private static Expression GetFilterExpressionLambda(Expression left, Expression right, FilterOperations operation)
{
Expression body;
switch (operation)
{
case FilterOperations.eq:
// {model.Id == 1}
body = Expression.Equal(left, right);
break;
case FilterOperations.lt:
// {model.Id < 1}
body = Expression.LessThan(left, right);
break;
case FilterOperations.gt:
// {model.Id > 1}
body = Expression.GreaterThan(left, right);
break;
case FilterOperations.le:
// {model.Id <= 1}
body = Expression.LessThanOrEqual(left, right);
break;
case FilterOperations.ge:
// {model.Id <= 1}
body = Expression.GreaterThanOrEqual(left, right);
break;
case FilterOperations.like:
// {model.Id <= 1}
body = Expression.Call(left, "Contains", null, right);
break;
default:
throw new JsonApiException("500", $"Unknown filter operation {operation}");
}

return body;
}


public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, IEnumerable<string> columns)
{
if(columns == null || columns.Count() == 0)
if (columns == null || columns.Count() == 0)
return source;

var sourceType = source.ElementType;

var resultType = typeof(TSource);

// {model}
var parameter = Expression.Parameter(sourceType, "model");

var bindings = columns.Select(column => Expression.Bind(
resultType.GetProperty(column), Expression.PropertyOrField(parameter, column)));

// { new Model () { Property = model.Property } }
var body = Expression.MemberInit(Expression.New(resultType), bindings);

// { model => new TodoItem() { Property = model.Property } }
var selector = Expression.Lambda(body, parameter);

return source.Provider.CreateQuery<TSource>(
Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
source.Expression, Expression.Quote(selector)));
Expand Down
21 changes: 15 additions & 6 deletions src/JsonApiDotNetCore/Internal/DasherizedRoutingConvention.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// REF: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.CustomRoutingConvention/NameSpaceRoutingConvention.cs
// REF: https://github.com/aspnet/Mvc/issues/5691
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Extensions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

Expand All @@ -12,17 +13,25 @@ public DasherizedRoutingConvention(string nspace)
{
_namespace = nspace;
}

public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var template = $"{_namespace}/{controller.ControllerName.Dasherize()}";
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
if (IsJsonApiController(controller))
{
Template = template
};
var template = $"{_namespace}/{controller.ControllerName.Dasherize()}";
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = template
};
}
}
}

private bool IsJsonApiController(ControllerModel controller)
{
return controller.ControllerType.IsSubclassOf(typeof(JsonApiControllerMixin));
}
}
}
72 changes: 30 additions & 42 deletions src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,39 @@
using System;
using System.Linq;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;

namespace JsonApiDotNetCore.Internal.Query
{
public class AttrFilterQuery
{
private readonly IJsonApiContext _jsonApiContext;

public AttrFilterQuery(
IJsonApiContext jsonApiCopntext,
FilterQuery filterQuery)
{
_jsonApiContext = jsonApiCopntext;

var attribute = GetAttribute(filterQuery.Key);

if (attribute == null)
throw new JsonApiException("400", $"{filterQuery.Key} is not a valid property.");

FilteredAttribute = attribute;
PropertyValue = filterQuery.Value;
FilterOperation = GetFilterOperation(filterQuery.Operation);
}

public AttrAttribute FilteredAttribute { get; set; }
public string PropertyValue { get; set; }
public FilterOperations FilterOperation { get; set; }

private FilterOperations GetFilterOperation(string prefix)
{
if (prefix.Length == 0) return FilterOperations.eq;

FilterOperations opertion;
if (!Enum.TryParse<FilterOperations>(prefix, out opertion))
throw new JsonApiException("400", $"Invalid filter prefix '{prefix}'");

return opertion;
}

private AttrAttribute GetAttribute(string propertyName)
public class AttrFilterQuery : BaseFilterQuery
{
return _jsonApiContext.RequestEntity.Attributes
.FirstOrDefault(attr =>
attr.InternalAttributeName.ToLower() == propertyName.ToLower()
);
private readonly IJsonApiContext _jsonApiContext;

public AttrFilterQuery(
IJsonApiContext jsonApiCopntext,
FilterQuery filterQuery)
{
_jsonApiContext = jsonApiCopntext;

var attribute = GetAttribute(filterQuery.Key);

if (attribute == null)
throw new JsonApiException("400", $"{filterQuery.Key} is not a valid property.");

FilteredAttribute = attribute;
PropertyValue = filterQuery.Value;
FilterOperation = GetFilterOperation(filterQuery.Operation);
}

public AttrAttribute FilteredAttribute { get; set; }
public string PropertyValue { get; set; }
public FilterOperations FilterOperation { get; set; }

private AttrAttribute GetAttribute(string propertyName)
{
return _jsonApiContext.RequestEntity.Attributes
.FirstOrDefault(attr =>
attr.InternalAttributeName.ToLower() == propertyName.ToLower()
);
}
}
}
}
18 changes: 18 additions & 0 deletions src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace JsonApiDotNetCore.Internal.Query
{
public class BaseFilterQuery
{
protected FilterOperations GetFilterOperation(string prefix)
{
if (prefix.Length == 0) return FilterOperations.eq;

FilterOperations opertion;
if (!Enum.TryParse<FilterOperations>(prefix, out opertion))
throw new JsonApiException("400", $"Invalid filter prefix '{prefix}'");

return opertion;
}
}
}
1 change: 1 addition & 0 deletions src/JsonApiDotNetCore/Internal/Query/FilterQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public FilterQuery(string key, string value, string operation)
public string Key { get; set; }
public string Value { get; set; }
public string Operation { get; set; }
public bool IsAttributeOfRelationship => Key.Contains(".");
}
}
51 changes: 51 additions & 0 deletions src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Linq;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;

namespace JsonApiDotNetCore.Internal.Query
{
public class RelatedAttrFilterQuery : BaseFilterQuery
{
private readonly IJsonApiContext _jsonApiContext;

public RelatedAttrFilterQuery(
IJsonApiContext jsonApiCopntext,
FilterQuery filterQuery)
{
_jsonApiContext = jsonApiCopntext;

var relationshipArray = filterQuery.Key.Split('.');

var relationship = GetRelationship(relationshipArray[0]);
if (relationship == null)
throw new JsonApiException("400", $"{relationshipArray[0]} is not a valid relationship.");

var attribute = GetAttribute(relationship, relationshipArray[1]);
if (attribute == null)
throw new JsonApiException("400", $"{relationshipArray[1]} is not a valid attribute on {relationshipArray[0]}.");

FilteredRelationship = relationship;
FilteredAttribute = attribute;
PropertyValue = filterQuery.Value;
FilterOperation = GetFilterOperation(filterQuery.Operation);
}

public AttrAttribute FilteredAttribute { get; set; }
public string PropertyValue { get; set; }
public FilterOperations FilterOperation { get; set; }
public RelationshipAttribute FilteredRelationship { get; private set; }

private RelationshipAttribute GetRelationship(string propertyName)
{
return _jsonApiContext.RequestEntity.Relationships
.FirstOrDefault(r => r.InternalRelationshipName.ToLower() == propertyName.ToLower());
}

private AttrAttribute GetAttribute(RelationshipAttribute relationship, string attribute)
{
var relatedContextExntity = _jsonApiContext.ContextGraph.GetContextEntity(relationship.Type);
return relatedContextExntity.Attributes
.FirstOrDefault(a => a.InternalAttributeName.ToLower() == attribute.ToLower());
}
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>1.3.0</VersionPrefix>
<VersionPrefix>1.3.1</VersionPrefix>
<TargetFramework>netcoreapp1.0</TargetFramework>
<AssemblyName>JsonApiDotNetCore</AssemblyName>
<PackageId>JsonApiDotNetCore</PackageId>
Expand Down
Loading

0 comments on commit 219ccfc

Please sign in to comment.