Skip to content

Various fixes #731

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f6237f8
Include type name in error when setting read-only property.
Apr 14, 2020
e15246d
Simpler tests with more complete assertions
Apr 14, 2020
48c6696
Fixed: re-fetch entity after CREATE without relationships
Apr 15, 2020
7b08441
Fixes in content negotation. Validations did not run when POSTing wit…
Apr 15, 2020
8b6a94e
Code cleanup
Apr 15, 2020
9d76d05
Fixed: relationships were not returned when using sparse fieldsets
Apr 16, 2020
8ff14c5
Fixed: Allow partially matching accept headers as defined in HTTP sta…
Apr 20, 2020
e82ab39
Fixed: fail on mismatch between ID in patch request body and ID in URL.
Apr 20, 2020
9239d61
Cleanup dead and duplicate code; moved essential registration to top-…
Apr 21, 2020
26faa2d
Scan the EF Core model for entity types (removed the requirement to e…
Apr 21, 2020
b55c295
Log resource graph validation warnings at configuration time instead …
Apr 21, 2020
397e43a
Added PropertyInfo to RelationshipAttribute
Apr 22, 2020
e523a3e
Collections: type widening
Apr 22, 2020
57e07f9
Hooks fix
Apr 22, 2020
958cc99
Fixed: serializer issue where tests influence each other (they fail t…
Apr 22, 2020
74aa7df
Flexible collection types/interfaces on HasMany attributes
Apr 23, 2020
bd4dda1
Flexible collection types/interfaces on HasManyThrough attributes
Apr 23, 2020
ca79de7
Cleanup collections
Apr 23, 2020
90c2432
Cleanup around type helpers
Apr 23, 2020
1fb69ae
Removed unneeded ToList/ToArray calls
Apr 23, 2020
19b9918
More types cleanup
Apr 23, 2020
6cb4cc2
Merge branch 'master' into various-fixes
Apr 29, 2020
b109ee4
Renamed CurrentRequestMiddleware to JsonApiMiddleware
Apr 29, 2020
29e6edd
Fixed broken build
May 4, 2020
ff9c287
Added injectable ISystemClock to AppDbContext, which is used by User …
May 6, 2020
2697b4a
Updated tests no never use the real clock
May 6, 2020
7880f67
Added ISystemClock dependency to Passport
May 6, 2020
7cf2b9a
Fixes for usage of obfuscated IDs
May 7, 2020
04aff35
Added dynamic query building for resources with constructor parameters
May 7, 2020
606f627
More fixes for resources with parameterized constructors
May 7, 2020
8fe35df
Even more fixes for resources with parameterized constructors
May 7, 2020
ff70656
Fix rendering StringId in errors for injected resources
May 7, 2020
a636422
Include resource name in error message
May 7, 2020
69e3e33
Added unit tests for create resource expressions
May 7, 2020
4834612
Merge branch 'master' into various-fixes
May 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion benchmarks/DependencyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Query;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;

namespace Benchmarks
Expand All @@ -12,7 +13,7 @@ internal static class DependencyFactory
{
public static IResourceGraph CreateResourceGraph(IJsonApiOptions options)
{
IResourceGraphBuilder builder = new ResourceGraphBuilder(options);
IResourceGraphBuilder builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
builder.AddResource<BenchmarkResource>(BenchmarkResourcePublicNames.Type);
return builder.Build();
}
Expand Down
4 changes: 3 additions & 1 deletion benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Serialization;
Expand Down Expand Up @@ -37,7 +39,7 @@ public JsonApiDeserializerBenchmarks()
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
var targetedFields = new TargetedFields();

_jsonApiDeserializer = new RequestDeserializer(resourceGraph, targetedFields);
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields);
}

[Benchmark]
Expand Down
1 change: 0 additions & 1 deletion benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Managers;
using JsonApiDotNetCore.Query;
Expand Down
3 changes: 2 additions & 1 deletion src/Examples/GettingStarted/Models/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ public sealed class Person : Identifiable
{
[Attr]
public string Name { get; set; }

[HasMany]
public List<Article> Articles { get; set; }
public ICollection<Article> Articles { get; set; }
}
}
2 changes: 1 addition & 1 deletion src/Examples/GettingStarted/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore;

namespace GettingStarted
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
using JsonApiDotNetCore.Configuration;
using System.Threading.Tasks;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCoreExample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExample.Controllers
{
public sealed class PassportsController : JsonApiController<Passport>
public sealed class PassportsController : BaseJsonApiController<Passport>
{
public PassportsController(
IJsonApiOptions jsonApiOptions,
ILoggerFactory loggerFactory,
IResourceService<Passport, int> resourceService)
: base(jsonApiOptions, loggerFactory, resourceService)
{ }

[HttpGet]
public override async Task<IActionResult> GetAsync() => await base.GetAsync();

[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(string id)
{
int idValue = HexadecimalObfuscationCodec.Decode(id);
return await base.GetAsync(idValue);
}

[HttpPatch("{id}")]
public async Task<IActionResult> PatchAsync(string id, [FromBody] Passport entity)
{
int idValue = HexadecimalObfuscationCodec.Decode(id);
return await base.PatchAsync(idValue, entity);
}

[HttpPost]
public override async Task<IActionResult> PostAsync([FromBody] Passport entity)
{
return await base.PostAsync(entity);
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteAsync(string id)
{
int idValue = HexadecimalObfuscationCodec.Decode(id);
return await base.DeleteAsync(idValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,25 @@ public IActionResult Get()
var result = new[] { "value" };
return Ok(result);
}

[HttpPost]
public IActionResult Post(string name)
{
var result = "Hello, " + name;
return Ok(result);
}

[HttpPatch]
public IActionResult Patch(string name)
{
var result = "Hello, " + name;
return Ok(result);
}

[HttpDelete]
public IActionResult Delete()
{
return Ok("Deleted");
}
}
}
20 changes: 15 additions & 5 deletions src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
using System;
using JsonApiDotNetCoreExample.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCoreExample.Data
{
public sealed class AppDbContext : DbContext
{
public ISystemClock SystemClock { get; }

public DbSet<TodoItem> TodoItems { get; set; }
public DbSet<Passport> Passports { get; set; }
public DbSet<Person> People { get; set; }
public DbSet<TodoItemCollection> TodoItemCollections { get; set; }
public DbSet<KebabCasedModel> KebabCasedModels { get; set; }
public DbSet<Article> Articles { get; set; }
public DbSet<Author> AuthorDifferentDbContextName { get; set; }
public DbSet<NonJsonApiResource> NonJsonApiResources { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<SuperUser> SuperUsers { get; set; }
public DbSet<PersonRole> PersonRoles { get; set; }
public DbSet<ArticleTag> ArticleTags { get; set; }
public DbSet<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<ThrowingResource> ThrowingResources { get; set; }

public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public AppDbContext(DbContextOptions<AppDbContext> options, ISystemClock systemClock) : base(options)
{
SystemClock = systemClock ?? throw new ArgumentNullException(nameof(systemClock));
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ThrowingResource>();

modelBuilder.Entity<SuperUser>().HasBaseType<User>();

modelBuilder.Entity<TodoItem>()
Expand Down Expand Up @@ -66,6 +71,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasForeignKey<Person>(p => p.PassportId)
.OnDelete(DeleteBehavior.SetNull);

modelBuilder.Entity<Passport>()
.HasMany(passport => passport.GrantedVisas)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);

modelBuilder.Entity<TodoItem>()
.HasOne(p => p.OneToOnePerson)
.WithOne(p => p.OneToOneTodoItem)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace JsonApiDotNetCoreExample
{
public static class HexadecimalObfuscationCodec
{
public static int Decode(string value)
{
if (string.IsNullOrEmpty(value))
{
return 0;
}

if (!value.StartsWith("x"))
{
throw new InvalidOperationException("Invalid obfuscated id.");
}

string stringValue = FromHexString(value.Substring(1));
return int.Parse(stringValue);
}

private static string FromHexString(string hexString)
{
List<byte> bytes = new List<byte>(hexString.Length / 2);
for (int index = 0; index < hexString.Length; index += 2)
{
var hexChar = hexString.Substring(index, 2);
byte bt = byte.Parse(hexChar, NumberStyles.HexNumber);
bytes.Add(bt);
}

var chars = Encoding.ASCII.GetChars(bytes.ToArray());
return new string(chars);
}

public static string Encode(object value)
{
if (value is int intValue && intValue == 0)
{
return string.Empty;
}

string stringValue = value.ToString();
return 'x' + ToHexString(stringValue);
}

private static string ToHexString(string value)
{
var builder = new StringBuilder();

foreach (byte bt in Encoding.ASCII.GetBytes(value))
{
builder.Append(bt.ToString("X2"));
}

return builder.ToString();
}
}
}
9 changes: 4 additions & 5 deletions src/Examples/JsonApiDotNetCoreExample/Models/Article.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ public sealed class Article : Identifiable

[NotMapped]
[HasManyThrough(nameof(ArticleTags))]
public List<Tag> Tags { get; set; }
public List<ArticleTag> ArticleTags { get; set; }

public ISet<Tag> Tags { get; set; }
public ISet<ArticleTag> ArticleTags { get; set; }

[NotMapped]
[HasManyThrough(nameof(IdentifiableArticleTags))]
public List<Tag> IdentifiableTags { get; set; }
public List<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
public ICollection<Tag> IdentifiableTags { get; set; }
public ICollection<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
}
}
8 changes: 7 additions & 1 deletion src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCoreExample.Data;

namespace JsonApiDotNetCoreExample.Models
{
Expand All @@ -9,8 +11,12 @@ public sealed class ArticleTag

public int TagId { get; set; }
public Tag Tag { get; set; }
}

public ArticleTag(AppDbContext appDbContext)
{
if (appDbContext == null) throw new ArgumentNullException(nameof(appDbContext));
}
}

public class IdentifiableArticleTag : Identifiable
{
Expand Down
2 changes: 1 addition & 1 deletion src/Examples/JsonApiDotNetCoreExample/Models/Author.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public sealed class Author : Identifiable
public string Name { get; set; }

[HasMany]
public List<Article> Articles { get; set; }
public IList<Article> Articles { get; set; }
}
}

29 changes: 22 additions & 7 deletions src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCoreExample.Data;
using Microsoft.AspNetCore.Authentication;

namespace JsonApiDotNetCoreExample.Models
{
public class Passport : Identifiable
{
private readonly ISystemClock _systemClock;
private int? _socialSecurityNumber;

protected override string GetStringId(object value)
{
return HexadecimalObfuscationCodec.Encode(value);
}

protected override int GetTypedId(string value)
{
return HexadecimalObfuscationCodec.Decode(value);
}

[Attr]
public int? SocialSecurityNumber
{
Expand All @@ -18,7 +31,7 @@ public int? SocialSecurityNumber
{
if (value != _socialSecurityNumber)
{
LastSocialSecurityNumberChange = DateTime.Now;
LastSocialSecurityNumberChange = _systemClock.UtcNow.LocalDateTime;
_socialSecurityNumber = value;
}
}
Expand Down Expand Up @@ -54,14 +67,16 @@ public string BirthCountryName

[Attr(AttrCapabilities.All & ~AttrCapabilities.AllowMutate)]
[NotMapped]
public string GrantedVisaCountries
{
get => GrantedVisas == null ? null : string.Join(", ", GrantedVisas.Select(v => v.TargetCountry.Name));
// The setter is required only for deserialization in unit tests.
set { }
}
public string GrantedVisaCountries => GrantedVisas == null || !GrantedVisas.Any()
? null
: string.Join(", ", GrantedVisas.Select(v => v.TargetCountry.Name));

[EagerLoad]
public ICollection<Visa> GrantedVisas { get; set; }

public Passport(AppDbContext appDbContext)
{
_systemClock = appDbContext.SystemClock;
}
}
}
6 changes: 3 additions & 3 deletions src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ public string FirstName
public Gender Gender { get; set; }

[HasMany]
public List<TodoItem> TodoItems { get; set; }
public ISet<TodoItem> TodoItems { get; set; }

[HasMany]
public List<TodoItem> AssignedTodoItems { get; set; }
public ISet<TodoItem> AssignedTodoItems { get; set; }

[HasMany]
public List<TodoItemCollection> todoCollections { get; set; }
public HashSet<TodoItemCollection> todoCollections { get; set; }

[HasOne]
public PersonRole Role { get; set; }
Expand Down
Loading