From 9ddea5b5c3dfeba599192ee70e8ad055d4a4cfba Mon Sep 17 00:00:00 2001 From: Simon Hofer Date: Tue, 13 Sep 2016 08:26:32 +0200 Subject: [PATCH 1/3] a first try to make queryable modifiable by customer --- .../Data/CommentSearch.csv | 6 ++ .../Data/PostSearch.csv | 5 + .../FetchingResourcesQueryResolverTests.cs | 54 +++++++++++ .../GetAllResponse.json | 96 +++++++++++++++++++ .../GetWithFilterResponse.json | 27 ++++++ .../GetWithSearchFilterResponse.json | 73 ++++++++++++++ ...sts.EntityFrameworkTestWebApp.Tests.csproj | 10 ++ .../Helper/PredicateBuilder.cs | 48 ++++++++++ ...anceTests.EntityFrameworkTestWebApp.csproj | 5 + .../Models/CommentSearch.cs | 28 ++++++ .../Models/PostSearch.cs | 29 ++++++ .../Models/TestDbContext.cs | 2 + .../PostSearchResourceQueryResolver.cs | 40 ++++++++ .../Startup.cs | 2 + JSONAPI.Autofac/JsonApiAutofacModule.cs | 17 +++- .../EntityFrameworkDocumentMaterializer.cs | 14 ++- .../IResourceTypeConfiguration.cs | 2 + .../IResourceTypeConfigurator.cs | 5 +- JSONAPI/Configuration/JsonApiConfiguration.cs | 3 +- .../ResourceTypeConfiguration.cs | 8 ++ JSONAPI/JSONAPI.csproj | 1 + .../IResourceCollectionResolver.cs | 15 +++ 22 files changed, 483 insertions(+), 7 deletions(-) create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/CommentSearch.csv create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Data/PostSearch.csv create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetAllResponse.json create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithFilterResponse.json create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilterResponse.json create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Helper/PredicateBuilder.cs create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/CommentSearch.cs create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Models/PostSearch.cs create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/PostSearchResourceQueryResolver.cs create mode 100644 JSONAPI/QueryableResolvers/IResourceCollectionResolver.cs 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..9fce0954 --- /dev/null +++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs @@ -0,0 +1,54 @@ +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); + } + } + + } +} 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/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj index 35d82430..83a3a13f 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,6 +283,9 @@ + + + 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..b733b686 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,7 @@ + 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..267a82c2 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.RegisterEntityFrameworkResourceType(); return configuration; } diff --git a/JSONAPI.Autofac/JsonApiAutofacModule.cs b/JSONAPI.Autofac/JsonApiAutofacModule.cs index 9c57ca4a..685a9bb6 100644 --- a/JSONAPI.Autofac/JsonApiAutofacModule.cs +++ b/JSONAPI.Autofac/JsonApiAutofacModule.cs @@ -8,6 +8,7 @@ using JSONAPI.Documents.Builders; using JSONAPI.Http; using JSONAPI.Json; +using JSONAPI.QueryableResolvers; using JSONAPI.QueryableTransformers; namespace JSONAPI.Autofac @@ -38,6 +39,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 +71,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 +90,12 @@ 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 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); diff --git a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs index 114f7877..b30a89a9 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 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/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/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); + } +} From 0459ed8a9902085cfa14a03733a684323695accb Mon Sep 17 00:00:00 2001 From: Simon Hofer Date: Wed, 14 Sep 2016 10:36:29 +0200 Subject: [PATCH 2/3] make queryable modifiable on related --- JSONAPI.Autofac/JsonApiAutofacModule.cs | 14 ++++++++++---- .../Http/EntityFrameworkDocumentMaterializer.cs | 2 +- ...orkToManyRelatedResourceDocumentMaterializer.cs | 10 +++++++++- ...bleToManyRelatedResourceDocumentMaterializer.cs | 6 ++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/JSONAPI.Autofac/JsonApiAutofacModule.cs b/JSONAPI.Autofac/JsonApiAutofacModule.cs index 685a9bb6..dee18c31 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; @@ -90,11 +91,11 @@ 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)}; if (configuration.ResourceCollectionResolverType != null) { var collectionResolver = context.Resolve(configuration.ResourceCollectionResolverType, parameters); - parameters = new Parameter[] { new TypedParameter(typeof(IResourceTypeRegistration), registration), new NamedParameter("collectionResolver", collectionResolver), }; + parameters.Add(new NamedParameter("collectionResolver", collectionResolver)); } if (configuration.DocumentMaterializerType != null) return (IDocumentMaterializer)context.Resolve(configuration.DocumentMaterializerType, parameters); @@ -114,12 +115,17 @@ 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) }; - + var relConfiguration = context.ResolveKeyed(relationshipName); + 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; if (configuration.RelationshipConfigurations.TryGetValue(relationship.Property.Name, diff --git a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs index b30a89a9..a1996e36 100644 --- a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs +++ b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs @@ -28,7 +28,7 @@ public class EntityFrameworkDocumentMaterializer : IDocumentMaterializer wher private readonly ISortExpressionExtractor _sortExpressionExtractor; private readonly IIncludeExpressionExtractor _includeExpressionExtractor; private readonly IBaseUrlService _baseUrlService; - private IResourceCollectionResolver _collectionResolver; + private readonly IResourceCollectionResolver _collectionResolver; /// /// Creates a new EntityFrameworkDocumentMaterializer 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/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) From 0f326723200454b7679d97fae367f05d5abb8256 Mon Sep 17 00:00:00 2001 From: Simon Hofer Date: Wed, 14 Sep 2016 11:21:30 +0200 Subject: [PATCH 3/3] added tests for related / improvements --- .../FetchingResourcesQueryResolverTests.cs | 15 ++++++++ ...SearchFilter_related_to_many_Response.json | 20 ++++++++++ ...sts.EntityFrameworkTestWebApp.Tests.csproj | 1 + .../CommentSearchResourceQueryResolver.cs | 38 +++++++++++++++++++ ...anceTests.EntityFrameworkTestWebApp.csproj | 1 + .../Startup.cs | 2 +- JSONAPI.Autofac/JsonApiAutofacModule.cs | 18 ++++++--- 7 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/Fixtures/FetchingResourcesQueryResolver/GetWithSearchFilter_related_to_many_Response.json create mode 100644 JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/CommentSearchResourceQueryResolver.cs diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs index 9fce0954..89e9b4f0 100644 --- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs +++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/FetchingResourcesQueryResolverTests.cs @@ -50,5 +50,20 @@ public async Task GetWithSearchFilter() // this enables logic in Query resolver } } + + [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/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 83a3a13f..e1cc107f 100644 --- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj +++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.Tests.csproj @@ -291,6 +291,7 @@ + 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/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj index b733b686..39893c40 100644 --- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj +++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp.csproj @@ -157,6 +157,7 @@ + diff --git a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs index 267a82c2..f444f287 100644 --- a/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs +++ b/JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp/Startup.cs @@ -94,7 +94,7 @@ internal JsonApiConfiguration BuildConfiguration() configuration.RegisterEntityFrameworkResourceType(); configuration.RegisterEntityFrameworkResourceType(); configuration.RegisterResourceType().ResolveCollectionWith(); - configuration.RegisterEntityFrameworkResourceType(); + configuration.RegisterResourceType().ResolveCollectionWith(); return configuration; } diff --git a/JSONAPI.Autofac/JsonApiAutofacModule.cs b/JSONAPI.Autofac/JsonApiAutofacModule.cs index dee18c31..6b81653c 100644 --- a/JSONAPI.Autofac/JsonApiAutofacModule.cs +++ b/JSONAPI.Autofac/JsonApiAutofacModule.cs @@ -92,11 +92,14 @@ protected override void Load(ContainerBuilder builder) var configuration = context.ResolveKeyed(clrType); var registration = registry.GetRegistrationForType(clrType); 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); @@ -120,12 +123,17 @@ protected override void Load(ContainerBuilder builder) new TypedParameter(typeof(IResourceTypeRegistration), registration), new TypedParameter(typeof(ResourceTypeRelationship), relationship) }; - var relConfiguration = context.ResolveKeyed(relationshipName); - if (relConfiguration.ResourceCollectionResolverType != null) - { - var collectionResolver = context.Resolve(relConfiguration.ResourceCollectionResolverType, parameters); - parameters.Add(new NamedParameter("collectionResolver", collectionResolver)); + + // 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; if (configuration.RelationshipConfigurations.TryGetValue(relationship.Property.Name,