diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs index 8acf87c405..d140b23b6b 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs @@ -20,6 +20,9 @@ public class Person : Identifiable, IHasMeta [HasMany("todo-collections")] public virtual List TodoItemCollections { get; set; } + + [HasOne("unincludeable-item", Link.All, canInclude: false)] + public virtual TodoItem UnIncludeableItem { get; set; } public Dictionary GetMeta(IJsonApiContext context) { diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index e551bb9491..b6bcda29b3 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -131,11 +131,18 @@ public virtual IQueryable Include(IQueryable entities, string { var entity = _jsonApiContext.RequestEntity; var relationship = entity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == relationshipName); - if (relationship != null) - return entities.Include(relationship.InternalRelationshipName); + if (relationship == null) + { + throw new JsonApiException(400, $"Invalid relationship {relationshipName} on {entity.EntityName}", + $"{entity.EntityName} does not have a relationship named {relationshipName}"); + } + + if (!relationship.CanInclude) + { + throw new JsonApiException(400, $"Including the relationship {relationshipName} on {entity.EntityName} is not allowed"); + } + return entities.Include(relationship.InternalRelationshipName); - throw new JsonApiException(400, $"Invalid relationship {relationshipName} on {entity.EntityName}", - $"{entity.EntityName} does not have a relationship named {relationshipName}"); } public virtual async Task> PageAsync(IQueryable entities, int pageSize, int pageNumber) diff --git a/src/JsonApiDotNetCore/Models/HasManyAttribute.cs b/src/JsonApiDotNetCore/Models/HasManyAttribute.cs index b4fd1b42ec..4519dc8cb6 100644 --- a/src/JsonApiDotNetCore/Models/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Models/HasManyAttribute.cs @@ -2,8 +2,8 @@ namespace JsonApiDotNetCore.Models { public class HasManyAttribute : RelationshipAttribute { - public HasManyAttribute(string publicName, Link documentLinks = Link.All) - : base(publicName, documentLinks) + public HasManyAttribute(string publicName, Link documentLinks = Link.All, bool canInclude = true) + : base(publicName, documentLinks, canInclude) { } public override void SetValue(object entity, object newValue) diff --git a/src/JsonApiDotNetCore/Models/HasOneAttribute.cs b/src/JsonApiDotNetCore/Models/HasOneAttribute.cs index 0dd20e73e7..f863c8819b 100644 --- a/src/JsonApiDotNetCore/Models/HasOneAttribute.cs +++ b/src/JsonApiDotNetCore/Models/HasOneAttribute.cs @@ -2,8 +2,8 @@ namespace JsonApiDotNetCore.Models { public class HasOneAttribute : RelationshipAttribute { - public HasOneAttribute(string publicName, Link documentLinks = Link.All) - : base(publicName, documentLinks) + public HasOneAttribute(string publicName, Link documentLinks = Link.All, bool canInclude = true) + : base(publicName, documentLinks, canInclude) { } public override void SetValue(object entity, object newValue) diff --git a/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs index 0dbe6a4670..2781ecfb53 100644 --- a/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs @@ -4,10 +4,11 @@ namespace JsonApiDotNetCore.Models { public abstract class RelationshipAttribute : Attribute { - protected RelationshipAttribute(string publicName, Link documentLinks) + protected RelationshipAttribute(string publicName, Link documentLinks, bool canInclude) { PublicRelationshipName = publicName; DocumentLinks = documentLinks; + CanInclude = canInclude; } public string PublicRelationshipName { get; } @@ -16,6 +17,7 @@ protected RelationshipAttribute(string publicName, Link documentLinks) public bool IsHasMany => GetType() == typeof(HasManyAttribute); public bool IsHasOne => GetType() == typeof(HasOneAttribute); public Link DocumentLinks { get; } = Link.All; + public bool CanInclude { get; } public abstract void SetValue(object entity, object newValue); @@ -26,8 +28,7 @@ public override string ToString() public override bool Equals(object obj) { - var attr = obj as RelationshipAttribute; - if (attr == null) + if (!(obj is RelationshipAttribute attr)) { return false; } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs index a9aa9c4e67..343526f7d8 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs @@ -335,5 +335,29 @@ public async Task Request_ToIncludeDeeplyNestedRelationships_Returns_400() // assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } + + [Fact] + public async Task Request_ToIncludeRelationshipMarkedCanIncludeFalse_Returns_400() + { + // arrange + var person = _context.People.First(); + + var builder = new WebHostBuilder() + .UseStartup(); + + var httpMethod = new HttpMethod("GET"); + + var route = $"/api/v1/people/{person.Id}?include=unincludeable-item"; + + var server = new TestServer(builder); + var client = server.CreateClient(); + var request = new HttpRequestMessage(httpMethod, route); + + // act + var response = await client.SendAsync(request); + + // assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } } }