diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/CommentSearch.csv b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/CommentSearch.csv
new file mode 100644
index 00000000..f36413a2
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/CommentSearch.csv
@@ -0,0 +1,6 @@
+Id,Text,Created,PostId,AuthorId
+"101","Comment 1","2015-01-31T14:30Z","201","403"
+"102","efgComment","2015-01-31T14:35Z","201","402"
+"103","Comment 3","2015-01-31T14:41Z","201","403"
+"104","Comment 4","2015-02-05T09:08Z","202","403"
+"105","Comment 5","2015-02-06T14:19Z","203","401"
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/PostSearch.csv b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/PostSearch.csv
new file mode 100644
index 00000000..fe70ae43
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/PostSearch.csv
@@ -0,0 +1,5 @@
+Id,Title,Content,Created,AuthorId
+"201","Post abc","Post def content","2015-01-31T14:00Z","401"
+"202","Post efg","Post cde content","2015-02-05T08:10Z","401"
+"203","Post hij","Post efg content","2015-02-07T11:11Z","401"
+"204","Post klm","Post hij content","2015-02-08T06:59Z","402"
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs
new file mode 100644
index 00000000..89e9b4f0
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs
@@ -0,0 +1,69 @@
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests
+{
+ [TestClass]
+ public class FetchingResourcesQueryResolverTests : AcceptanceTestsBase
+ {
+ [TestMethod]
+ [DeploymentItem(@"Data\CommentSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\PostSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\User.csv", @"Data")]
+ public async Task GetAll()
+ {
+ using (var effortConnection = GetEffortConnection())
+ {
+ var response = await SubmitGet(effortConnection, "post-searchs");
+
+ await AssertResponseContent(response, @"Fixtures\FetchingResourcesQueryResolver\GetAllResponse.json", HttpStatusCode.OK);
+ }
+ }
+
+ [TestMethod]
+ [DeploymentItem(@"Data\CommentSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\PostSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\User.csv", @"Data")]
+ public async Task GetWithFilter()
+ {
+ using (var effortConnection = GetEffortConnection())
+ {
+ var response = await SubmitGet(effortConnection, "post-searchs?filter[title]=Post efg");
+
+ await AssertResponseContent(response, @"Fixtures\FetchingResourcesQueryResolver\GetWithFilterResponse.json", HttpStatusCode.OK);
+ }
+ }
+
+
+ [TestMethod]
+ [DeploymentItem(@"Data\CommentSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\PostSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\User.csv", @"Data")]
+ public async Task GetWithSearchFilter() // this enables logic in Query resolver
+ {
+ using (var effortConnection = GetEffortConnection())
+ {
+ var response = await SubmitGet(effortConnection, "post-searchs?searchterm=efg");
+
+ await AssertResponseContent(response, @"Fixtures\FetchingResourcesQueryResolver\GetWithSearchFilterResponse.json", HttpStatusCode.OK);
+ }
+ }
+
+
+ [TestMethod]
+ [DeploymentItem(@"Data\CommentSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\PostSearch.csv", @"Data")]
+ [DeploymentItem(@"Data\User.csv", @"Data")]
+ public async Task GetWithSearchFilter_related_to_many() // this enables logic in Query resolver for related
+ {
+ using (var effortConnection = GetEffortConnection())
+ {
+ var response = await SubmitGet(effortConnection, "post-searchs/201/comments?searchterm=efg");
+
+ await AssertResponseContent(response, @"Fixtures\FetchingResourcesQueryResolver\GetWithSearchFilter_related_to_many_Response.json", HttpStatusCode.OK);
+ }
+ }
+
+ }
+}
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetAllResponse.json b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetAllResponse.json
new file mode 100644
index 00000000..a9d4745e
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetAllResponse.json
@@ -0,0 +1,96 @@
+{
+ "data": [
+ {
+ "type": "post-searchs",
+ "id": "201",
+ "attributes": {
+ "content": "Post def content",
+ "created": "2015-01-31T14:00:00.0000000+00:00",
+ "title": "Post abc"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/201/relationships/author",
+ "related": "https://www.example.com/post-searchs/201/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/201/relationships/comments",
+ "related": "https://www.example.com/post-searchs/201/comments"
+ }
+ }
+ }
+ },
+ {
+ "type": "post-searchs",
+ "id": "202",
+ "attributes": {
+ "content": "Post cde content",
+ "created": "2015-02-05T08:10:00.0000000+00:00",
+ "title": "Post efg"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/202/relationships/author",
+ "related": "https://www.example.com/post-searchs/202/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/202/relationships/comments",
+ "related": "https://www.example.com/post-searchs/202/comments"
+ }
+ }
+ }
+ },
+ {
+ "type": "post-searchs",
+ "id": "203",
+ "attributes": {
+ "content": "Post efg content",
+ "created": "2015-02-07T11:11:00.0000000+00:00",
+ "title": "Post hij"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/203/relationships/author",
+ "related": "https://www.example.com/post-searchs/203/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/203/relationships/comments",
+ "related": "https://www.example.com/post-searchs/203/comments"
+ }
+ }
+ }
+ },
+ {
+ "type": "post-searchs",
+ "id": "204",
+ "attributes": {
+ "content": "Post hij content",
+ "created": "2015-02-08T06:59:00.0000000+00:00",
+ "title": "Post klm"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/204/relationships/author",
+ "related": "https://www.example.com/post-searchs/204/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/204/relationships/comments",
+ "related": "https://www.example.com/post-searchs/204/comments"
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithFilterResponse.json b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithFilterResponse.json
new file mode 100644
index 00000000..08edb94f
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithFilterResponse.json
@@ -0,0 +1,27 @@
+{
+ "data": [
+ {
+ "type": "post-searchs",
+ "id": "202",
+ "attributes": {
+ "content": "Post cde content",
+ "created": "2015-02-05T08:10:00.0000000+00:00",
+ "title": "Post efg"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/202/relationships/author",
+ "related": "https://www.example.com/post-searchs/202/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/202/relationships/comments",
+ "related": "https://www.example.com/post-searchs/202/comments"
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilterResponse.json b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilterResponse.json
new file mode 100644
index 00000000..4d1a71b2
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilterResponse.json
@@ -0,0 +1,73 @@
+{
+ "data": [
+ {
+ "type": "post-searchs",
+ "id": "201",
+ "attributes": {
+ "content": "Post def content",
+ "created": "2015-01-31T14:00:00.0000000+00:00",
+ "title": "Post abc"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/201/relationships/author",
+ "related": "https://www.example.com/post-searchs/201/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/201/relationships/comments",
+ "related": "https://www.example.com/post-searchs/201/comments"
+ }
+ }
+ }
+ },
+ {
+ "type": "post-searchs",
+ "id": "202",
+ "attributes": {
+ "content": "Post cde content",
+ "created": "2015-02-05T08:10:00.0000000+00:00",
+ "title": "Post efg"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/202/relationships/author",
+ "related": "https://www.example.com/post-searchs/202/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/202/relationships/comments",
+ "related": "https://www.example.com/post-searchs/202/comments"
+ }
+ }
+ }
+ },
+ {
+ "type": "post-searchs",
+ "id": "203",
+ "attributes": {
+ "content": "Post efg content",
+ "created": "2015-02-07T11:11:00.0000000+00:00",
+ "title": "Post hij"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/203/relationships/author",
+ "related": "https://www.example.com/post-searchs/203/author"
+ }
+ },
+ "comments": {
+ "links": {
+ "self": "https://www.example.com/post-searchs/203/relationships/comments",
+ "related": "https://www.example.com/post-searchs/203/comments"
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilter_related_to_many_Response.json b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilter_related_to_many_Response.json
new file mode 100644
index 00000000..3ad5dc11
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilter_related_to_many_Response.json
@@ -0,0 +1,20 @@
+{
+ "data": [
+ {
+ "type": "comment-searchs",
+ "id": "102",
+ "attributes": {
+ "created": "2015-01-31T14:35:00.0000000+00:00",
+ "text": "efgComment"
+ },
+ "relationships": {
+ "post": {
+ "links": {
+ "self": "https://www.example.com/comment-searchs/102/relationships/post",
+ "related": "https://www.example.com/comment-searchs/102/post"
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj
index 35d82430..e1cc107f 100644
--- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj
@@ -106,6 +106,7 @@
+
@@ -144,12 +145,18 @@
Always
+
+ Always
+
Always
Always
+
+ Always
+
Always
@@ -276,11 +283,15 @@
+
+
+
+
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/CommentSearchResourceQueryResolver.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/CommentSearchResourceQueryResolver.cs
new file mode 100644
index 00000000..bfd6011c
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/CommentSearchResourceQueryResolver.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Helper;
+using JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Models;
+using JSONAPI.QueryableResolvers;
+
+namespace JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp
+{
+ public class CommentSearchResourceQueryResolver : IResourceCollectionResolver
+ {
+ public Task> GetQueryForResourceCollection(IQueryable queryable, HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var queryPairs = request.GetQueryNameValuePairs();
+ foreach (var queryPair in queryPairs)
+ {
+ if (String.IsNullOrWhiteSpace(queryPair.Key))
+ continue;
+
+ if (!queryPair.Key.StartsWith("searchterm"))
+ continue;
+
+ var searchTerm = queryPair.Value;
+ var predicate = PredicateBuilder.False();
+
+ foreach (var str in Regex.Split(searchTerm, "\\s+"))
+ {
+ predicate = predicate.Or(y => y.Text.Contains(str));
+ }
+ queryable= queryable.Where(predicate);
+ }
+ return Task.FromResult(queryable);
+ }
+ }
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Helper/PredicateBuilder.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Helper/PredicateBuilder.cs
new file mode 100644
index 00000000..2a2b8283
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Helper/PredicateBuilder.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Linq.Expressions;
+
+namespace JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Helper
+{
+ public static class PredicateBuilder
+ {
+ public static Expression> True() { return f => true; }
+ public static Expression> False() { return f => false; }
+
+ public static Expression> Or(
+ this Expression> expr1,
+ Expression> expr2)
+ {
+ var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
+ return Expression.Lambda>
+ (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
+ }
+
+ public static Expression> And(
+ this Expression> expr1,
+ Expression> expr2)
+ {
+ var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
+ return Expression.Lambda>
+ (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
+ }
+ public static Expression Replace(this Expression expression, Expression searchEx, Expression replaceEx)
+ {
+ return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
+ }
+ }
+
+
+ internal class ReplaceVisitor : ExpressionVisitor
+ {
+ private readonly Expression _from, _to;
+ public ReplaceVisitor(Expression from, Expression to)
+ {
+ _from = from;
+ _to = to;
+ }
+ public override Expression Visit(Expression node)
+ {
+ return node == _from ? _to : base.Visit(node);
+ }
+ }
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj
index 0a5148ea..39893c40 100644
--- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj
@@ -21,6 +21,7 @@
..\
true
+
true
@@ -130,14 +131,17 @@
+
+
+
@@ -153,6 +157,8 @@
+
+
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/CommentSearch.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/CommentSearch.cs
new file mode 100644
index 00000000..94870773
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/CommentSearch.cs
@@ -0,0 +1,28 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Models
+{
+ public class CommentSearch
+ {
+ [Key]
+ public string Id { get; set; }
+
+ public string Text { get; set; }
+
+ public DateTimeOffset Created { get; set; }
+
+ [Required]
+ [JsonIgnore]
+ public string PostId { get; set; }
+
+ [Required]
+ [JsonIgnore]
+ public string AuthorId { get; set; }
+
+ [ForeignKey("PostId")]
+ public virtual PostSearch Post { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/PostSearch.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/PostSearch.cs
new file mode 100644
index 00000000..889b5135
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/PostSearch.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Newtonsoft.Json;
+
+namespace JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Models
+{
+ public class PostSearch
+ {
+ [Key]
+ public string Id { get; set; }
+
+ public string Title { get; set; }
+
+ public string Content { get; set; }
+
+ public DateTimeOffset Created { get; set; }
+
+ [JsonIgnore]
+ public string AuthorId { get; set; }
+
+ [ForeignKey("AuthorId")]
+ public virtual User Author { get; set; }
+
+ public virtual ICollection Comments { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/TestDbContext.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/TestDbContext.cs
index 4850d0a0..b2ba9afa 100644
--- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/TestDbContext.cs
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/TestDbContext.cs
@@ -38,8 +38,10 @@ protected override void OnModelCreating(DbModelBuilder modelBuilder)
public DbSet Buildings { get; set; }
public DbSet Companies { get; set; }
public DbSet Comments { get; set; }
+ public DbSet CommentSearchs { get; set; }
public DbSet Languages { get; set; }
public DbSet Posts { get; set; }
+ public DbSet PostSearchs { get; set; }
public DbSet PostsID { get; set; }
public DbSet PostsLongId { get; set; }
public DbSet Tags { get; set; }
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/PostSearchResourceQueryResolver.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/PostSearchResourceQueryResolver.cs
new file mode 100644
index 00000000..a17a4676
--- /dev/null
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/PostSearchResourceQueryResolver.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Helper;
+using JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Models;
+using JSONAPI.QueryableResolvers;
+
+namespace JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp
+{
+ public class PostSearchResourceQueryResolver: IResourceCollectionResolver
+ {
+ public Task> GetQueryForResourceCollection(IQueryable queryable, HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var queryPairs = request.GetQueryNameValuePairs();
+ foreach (var queryPair in queryPairs)
+ {
+ if (String.IsNullOrWhiteSpace(queryPair.Key))
+ continue;
+
+ if (!queryPair.Key.StartsWith("searchterm"))
+ continue;
+
+ var searchTerm = queryPair.Value;
+ var predicate = PredicateBuilder.False();
+
+ foreach (var str in Regex.Split(searchTerm, "\\s+"))
+ {
+ predicate = predicate.Or(x => x.Title.Contains(str));
+ predicate = predicate.Or(x => x.Content.ToString().Contains(str));
+ predicate = predicate.Or(x => x.Comments.Any(y => y.Text.Contains(str)));
+ }
+ queryable= queryable.Where(predicate);
+ }
+ return Task.FromResult(queryable);
+ }
+ }
+}
\ No newline at end of file
diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs
index 678f8532..f444f287 100644
--- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs
+++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs
@@ -93,6 +93,8 @@ internal JsonApiConfiguration BuildConfiguration()
configuration.RegisterResourceType();
configuration.RegisterEntityFrameworkResourceType();
configuration.RegisterEntityFrameworkResourceType();
+ configuration.RegisterResourceType().ResolveCollectionWith();
+ configuration.RegisterResourceType().ResolveCollectionWith();
return configuration;
}
diff --git a/JSONAPI.Autofac/JsonApiAutofacModule.cs b/JSONAPI.Autofac/JsonApiAutofacModule.cs
index 9c57ca4a..6b81653c 100644
--- a/JSONAPI.Autofac/JsonApiAutofacModule.cs
+++ b/JSONAPI.Autofac/JsonApiAutofacModule.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Autofac;
using Autofac.Core;
using JSONAPI.ActionFilters;
@@ -8,6 +9,7 @@
using JSONAPI.Documents.Builders;
using JSONAPI.Http;
using JSONAPI.Json;
+using JSONAPI.QueryableResolvers;
using JSONAPI.QueryableTransformers;
namespace JSONAPI.Autofac
@@ -38,6 +40,8 @@ protected override void Load(ContainerBuilder builder)
if (resourceTypeConfiguration.DocumentMaterializerType != null)
builder.RegisterType(resourceTypeConfiguration.DocumentMaterializerType);
+ if (resourceTypeConfiguration.ResourceCollectionResolverType != null)
+ builder.RegisterType(resourceTypeConfiguration.ResourceCollectionResolverType);
foreach (var relationship in resourceTypeRegistration.Relationships)
{
@@ -68,7 +72,12 @@ protected override void Load(ContainerBuilder builder)
{
var configuration = context.ResolveKeyed(resourceTypeName);
var registration = registry.GetRegistrationForResourceTypeName(resourceTypeName);
- var parameters = new Parameter[] { new TypedParameter(typeof (IResourceTypeRegistration), registration) };
+ var parameters = new Parameter[] { new TypedParameter(typeof (IResourceTypeRegistration), registration)};
+ if (configuration.ResourceCollectionResolverType != null)
+ {
+ var collectionResolver = context.Resolve(configuration.ResourceCollectionResolverType, parameters);
+ parameters = new Parameter[] { new TypedParameter(typeof(IResourceTypeRegistration), registration), new NamedParameter("collectionResolver", collectionResolver), };
+ }
if (configuration.DocumentMaterializerType != null)
return (IDocumentMaterializer)context.Resolve(configuration.DocumentMaterializerType, parameters);
return context.Resolve(parameters);
@@ -82,7 +91,15 @@ protected override void Load(ContainerBuilder builder)
{
var configuration = context.ResolveKeyed(clrType);
var registration = registry.GetRegistrationForType(clrType);
- var parameters = new Parameter[] { new TypedParameter(typeof(IResourceTypeRegistration), registration) };
+ var parameters = new List { new TypedParameter(typeof(IResourceTypeRegistration), registration)};
+
+ // add parameter for collectionResolver
+ if (configuration.ResourceCollectionResolverType != null)
+ {
+ var collectionResolver = context.Resolve(configuration.ResourceCollectionResolverType, parameters);
+ parameters.Add(new NamedParameter("collectionResolver", collectionResolver));
+ }
+
if (configuration.DocumentMaterializerType != null)
return (IDocumentMaterializer)context.Resolve(configuration.DocumentMaterializerType, parameters);
return context.Resolve(parameters);
@@ -101,11 +118,21 @@ protected override void Load(ContainerBuilder builder)
throw JsonApiException.CreateForNotFound(
string.Format("No relationship `{0}` exists for the resource type `{1}`.", relationshipName, resourceTypeName));
- var parameters = new Parameter[]
+ var parameters = new List
{
new TypedParameter(typeof(IResourceTypeRegistration), registration),
new TypedParameter(typeof(ResourceTypeRelationship), relationship)
};
+
+ // add parameter for collectionResolver
+ if (context.IsRegisteredWithKey(relationship.RelatedType)) {
+ var relConfiguration = context.ResolveKeyed(relationship.RelatedType);
+ if (relConfiguration.ResourceCollectionResolverType != null)
+ {
+ var collectionResolver = context.Resolve(relConfiguration.ResourceCollectionResolverType, parameters);
+ parameters.Add(new NamedParameter("collectionResolver", collectionResolver));
+ }
+ }
// First, see if they have set an explicit materializer for this relationship
IResourceTypeRelationshipConfiguration relationshipConfiguration;
diff --git a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs
index 114f7877..a1996e36 100644
--- a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs
+++ b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs
@@ -11,6 +11,7 @@
using JSONAPI.Documents.Builders;
using JSONAPI.Extensions;
using JSONAPI.Http;
+using JSONAPI.QueryableResolvers;
namespace JSONAPI.EntityFramework.Http
{
@@ -27,6 +28,7 @@ public class EntityFrameworkDocumentMaterializer : IDocumentMaterializer wher
private readonly ISortExpressionExtractor _sortExpressionExtractor;
private readonly IIncludeExpressionExtractor _includeExpressionExtractor;
private readonly IBaseUrlService _baseUrlService;
+ private readonly IResourceCollectionResolver _collectionResolver;
///
/// Creates a new EntityFrameworkDocumentMaterializer
@@ -39,7 +41,8 @@ public EntityFrameworkDocumentMaterializer(
IEntityFrameworkResourceObjectMaterializer entityFrameworkResourceObjectMaterializer,
ISortExpressionExtractor sortExpressionExtractor,
IIncludeExpressionExtractor includeExpressionExtractor,
- IBaseUrlService baseUrlService)
+ IBaseUrlService baseUrlService,
+ IResourceCollectionResolver collectionResolver = null)
{
DbContext = dbContext;
_resourceTypeRegistration = resourceTypeRegistration;
@@ -49,14 +52,19 @@ public EntityFrameworkDocumentMaterializer(
_sortExpressionExtractor = sortExpressionExtractor;
_includeExpressionExtractor = includeExpressionExtractor;
_baseUrlService = baseUrlService;
+ _collectionResolver = collectionResolver;
}
- public virtual Task GetRecords(HttpRequestMessage request, CancellationToken cancellationToken)
+ public virtual async Task GetRecords(HttpRequestMessage request, CancellationToken cancellationToken)
{
var sortExpressions = _sortExpressionExtractor.ExtractSortExpressions(request);
var includes = _includeExpressionExtractor.ExtractIncludeExpressions(request);
var query = QueryIncludeNavigationProperties(null, GetNavigationPropertiesIncludes(includes));
- return _queryableResourceCollectionDocumentBuilder.BuildDocument(query, request, sortExpressions, cancellationToken, includes);
+ if (_collectionResolver != null)
+ {
+ query = await _collectionResolver.GetQueryForResourceCollection(query, request, cancellationToken);
+ }
+ return await _queryableResourceCollectionDocumentBuilder.BuildDocument(query, request, sortExpressions, cancellationToken, includes);
}
public virtual async Task GetRecordById(string id, HttpRequestMessage request, CancellationToken cancellationToken)
diff --git a/JSONAPI.EntityFramework/Http/EntityFrameworkToManyRelatedResourceDocumentMaterializer.cs b/JSONAPI.EntityFramework/Http/EntityFrameworkToManyRelatedResourceDocumentMaterializer.cs
index 142075a0..37756ccc 100644
--- a/JSONAPI.EntityFramework/Http/EntityFrameworkToManyRelatedResourceDocumentMaterializer.cs
+++ b/JSONAPI.EntityFramework/Http/EntityFrameworkToManyRelatedResourceDocumentMaterializer.cs
@@ -9,6 +9,7 @@
using JSONAPI.Documents.Builders;
using JSONAPI.Extensions;
using JSONAPI.Http;
+using JSONAPI.QueryableResolvers;
namespace JSONAPI.EntityFramework.Http
{
@@ -21,6 +22,7 @@ public class EntityFrameworkToManyRelatedResourceDocumentMaterializer _collectionResolver;
///
/// Builds a new EntityFrameworkToManyRelatedResourceDocumentMaterializer.
@@ -31,12 +33,14 @@ public EntityFrameworkToManyRelatedResourceDocumentMaterializer(
IQueryableResourceCollectionDocumentBuilder queryableResourceCollectionDocumentBuilder,
ISortExpressionExtractor sortExpressionExtractor,
IIncludeExpressionExtractor includeExpressionExtractor,
- IResourceTypeRegistration primaryTypeRegistration)
+ IResourceTypeRegistration primaryTypeRegistration,
+ IResourceCollectionResolver collectionResolver = null)
: base(queryableResourceCollectionDocumentBuilder, sortExpressionExtractor, includeExpressionExtractor)
{
_relationship = relationship;
_dbContext = dbContext;
_primaryTypeRegistration = primaryTypeRegistration;
+ _collectionResolver = collectionResolver;
}
protected override async Task> GetRelatedQuery(string primaryResourceId,
@@ -56,6 +60,10 @@ protected override async Task> GetRelatedQuery(string prima
_primaryTypeRegistration.ResourceTypeName, primaryResourceId));
var includes = GetNavigationPropertiesIncludes(Includes);
var query = primaryEntityQuery.SelectMany(lambda);
+ if (_collectionResolver != null)
+ {
+ query = await _collectionResolver.GetQueryForResourceCollection(query, Request, cancellationToken);
+ }
if (includes != null && includes.Any())
query = includes.Aggregate(query, (current, include) => current.Include(include));
diff --git a/JSONAPI/Configuration/IResourceTypeConfiguration.cs b/JSONAPI/Configuration/IResourceTypeConfiguration.cs
index 9ee2a8ee..343990f1 100644
--- a/JSONAPI/Configuration/IResourceTypeConfiguration.cs
+++ b/JSONAPI/Configuration/IResourceTypeConfiguration.cs
@@ -50,5 +50,7 @@ public interface IResourceTypeConfiguration
///
///
IResourceTypeRegistration BuildResourceTypeRegistration();
+
+ Type ResourceCollectionResolverType { get; }
}
}
\ No newline at end of file
diff --git a/JSONAPI/Configuration/IResourceTypeConfigurator.cs b/JSONAPI/Configuration/IResourceTypeConfigurator.cs
index a861bbfe..aea2dd95 100644
--- a/JSONAPI/Configuration/IResourceTypeConfigurator.cs
+++ b/JSONAPI/Configuration/IResourceTypeConfigurator.cs
@@ -2,6 +2,7 @@
using System.Linq.Expressions;
using JSONAPI.Core;
using JSONAPI.Http;
+using JSONAPI.QueryableResolvers;
namespace JSONAPI.Configuration
{
@@ -42,7 +43,9 @@ void ConfigureRelationship(Expression> property,
///
/// Specifies a function to use build expressions that allow sorting resources of this type by ID
///
- void OverrideDefaultSortById(Func sortByIdExpressionFactory);
+ void OverrideDefaultSortById(Func sortByIdExpressionFactory);
+ IResourceTypeConfigurator ResolveCollectionWith()
+ where TCollectionResolver : IResourceCollectionResolver;
}
}
\ No newline at end of file
diff --git a/JSONAPI/Configuration/JsonApiConfiguration.cs b/JSONAPI/Configuration/JsonApiConfiguration.cs
index 27752c1a..74437cf8 100644
--- a/JSONAPI/Configuration/JsonApiConfiguration.cs
+++ b/JSONAPI/Configuration/JsonApiConfiguration.cs
@@ -57,12 +57,13 @@ public JsonApiConfiguration(IResourceTypeRegistrar resourceTypeRegistrar)
///
/// Registers a resource type with the configuration
///
- public void RegisterResourceType(Action> configurationAction = null)
+ public IResourceTypeConfigurator RegisterResourceType(Action> configurationAction = null)
{
var configuration = new ResourceTypeConfiguration(_resourceTypeRegistrar);
if (configurationAction != null)
configurationAction(configuration);
_resourceTypeConfigurations.Add(configuration);
+ return configuration;
}
///
diff --git a/JSONAPI/Configuration/ResourceTypeConfiguration.cs b/JSONAPI/Configuration/ResourceTypeConfiguration.cs
index 5bc0cc14..437ede78 100644
--- a/JSONAPI/Configuration/ResourceTypeConfiguration.cs
+++ b/JSONAPI/Configuration/ResourceTypeConfiguration.cs
@@ -5,6 +5,7 @@
using System.Reflection;
using JSONAPI.Core;
using JSONAPI.Http;
+using JSONAPI.QueryableResolvers;
namespace JSONAPI.Configuration
{
@@ -30,6 +31,7 @@ internal ResourceTypeConfiguration(IResourceTypeRegistrar resourceTypeRegistrar)
public IDictionary RelationshipConfigurations { get; private set; }
public Func FilterByIdExpressionFactory { get; private set; }
public Func SortByIdExpressionFactory { get; private set; }
+ public Type ResourceCollectionResolverType { get; private set; }
public void ConfigureRelationship(Expression> property,
Action configurationAction)
@@ -72,6 +74,12 @@ public void OverrideDefaultSortById(Func sortBy
SortByIdExpressionFactory = sortByIdExpressionFactory;
}
+ public IResourceTypeConfigurator ResolveCollectionWith() where TCollectionResolver : IResourceCollectionResolver
+ {
+ ResourceCollectionResolverType = typeof(TCollectionResolver);
+ return this;
+ }
+
public IResourceTypeRegistration BuildResourceTypeRegistration()
{
return _resourceTypeRegistrar.BuildRegistration(ClrType, ResourceTypeName, FilterByIdExpressionFactory,
diff --git a/JSONAPI/Http/QueryableToManyRelatedResourceDocumentMaterializer.cs b/JSONAPI/Http/QueryableToManyRelatedResourceDocumentMaterializer.cs
index 6aa2286f..d9c21247 100644
--- a/JSONAPI/Http/QueryableToManyRelatedResourceDocumentMaterializer.cs
+++ b/JSONAPI/Http/QueryableToManyRelatedResourceDocumentMaterializer.cs
@@ -20,6 +20,11 @@ public abstract class QueryableToManyRelatedResourceDocumentMaterializer
protected string[] Includes = {};
+ ///
+ /// The current http-request.
+ ///
+ protected HttpRequestMessage Request;
+
///
/// Creates a new QueryableRelatedResourceDocumentMaterializer
@@ -38,6 +43,7 @@ public async Task GetRelatedResourceDocument(string primaryRes
CancellationToken cancellationToken)
{
Includes = _includeExpressionExtractor.ExtractIncludeExpressions(request);
+ Request = request;
var query = await GetRelatedQuery(primaryResourceId, cancellationToken);
var sortExpressions = _sortExpressionExtractor.ExtractSortExpressions(request);
if (sortExpressions == null || sortExpressions.Length < 1)
diff --git a/JSONAPI/JSONAPI.csproj b/JSONAPI/JSONAPI.csproj
index 9bd3d2e2..a03d6d21 100644
--- a/JSONAPI/JSONAPI.csproj
+++ b/JSONAPI/JSONAPI.csproj
@@ -101,6 +101,7 @@
+
diff --git a/JSONAPI/QueryableResolvers/IResourceCollectionResolver.cs b/JSONAPI/QueryableResolvers/IResourceCollectionResolver.cs
new file mode 100644
index 00000000..98898d3a
--- /dev/null
+++ b/JSONAPI/QueryableResolvers/IResourceCollectionResolver.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace JSONAPI.QueryableResolvers
+{
+ public interface IResourceCollectionResolver
+ {
+ Task> GetQueryForResourceCollection(IQueryable queryable, HttpRequestMessage request, CancellationToken cancellationToken);
+ }
+}