diff --git a/Directory.Build.props b/Directory.Build.props
index 5b447710fc..5014190440 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,24 +1,23 @@
-
- netcoreapp2.2
- netstandard2.0
- 2.2.*
- 2.2.*
- 2.2.*
- 2.2.*
- 2.2.*
- 2.2.*
- 4.0.0
- 2.1.0
+ netcoreapp3.0
+ netstandard2.1
+ 3.*
+ 3.*
+ 3.*
+ 3.*
+ 3.*
+ 3.*
+ 4.1.1
+ 3.0.1
4.5.0
- 15.7.2
- 2.3.1
- 22.1.2
- 4.8.3
+ 16.3.0
+ 2.4.1
+ 28.4.1
+ 4.13.1
\ No newline at end of file
diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln
index 7310cf2097..ad130ce4b1 100644
--- a/JsonApiDotnetCore.sln
+++ b/JsonApiDotnetCore.sln
@@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\Js
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{067FFD7A-C66B-473D-8471-37F5C95DF61C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "test\IntegrationTests\IntegrationTests.csproj", "{CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -158,6 +160,18 @@ Global
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x64.Build.0 = Release|Any CPU
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.ActiveCfg = Release|Any CPU
{067FFD7A-C66B-473D-8471-37F5C95DF61C}.Release|x86.Build.0 = Release|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Debug|x64.Build.0 = Debug|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Debug|x86.Build.0 = Debug|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Release|x64.ActiveCfg = Release|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Release|x64.Build.0 = Release|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Release|x86.ActiveCfg = Release|Any CPU
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -173,6 +187,7 @@ Global
{789085E1-048F-4996-B600-791B9CA3A663} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
{8BCFF95F-4850-427C-AEDB-B5B4F62B2C7B} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
{21D27239-138D-4604-8E49-DCBE41BCE4C8} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
+ {CEB08B86-6BF1-4227-B20F-45AE9C1CC6D9} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4}
diff --git a/README.md b/README.md
index 3085181b1e..8126e7f1d8 100644
--- a/README.md
+++ b/README.md
@@ -53,10 +53,12 @@ public class Article : Identifiable
```csharp
public class ArticlesController : JsonApiController
{
- public ArticlesController(
- IJsonApiContext jsonApiContext,
- IResourceService resourceService)
- : base(jsonApiContext, resourceService) { }
+ public ArticlesController(
+ IJsonApiOptions jsonApiOptions,
+ IResourceService resourceService,
+ ILoggerFactory loggerFactory)
+ : base(jsonApiOptions, resourceService, loggerFactory)
+ { }
}
```
@@ -79,7 +81,7 @@ public class Startup
### Development
-Restore all nuget packages with:
+Restore all NuGet packages with:
```bash
dotnet restore
@@ -87,11 +89,10 @@ dotnet restore
#### Testing
-Running tests locally requires access to a postgresql database.
-If you have docker installed, this can be propped up via:
+Running tests locally requires access to a PostgreSQL database. If you have docker installed, this can be propped up via:
```bash
-docker run --rm --name jsonapi-dotnet-core-testing -e POSTGRES_DB=JsonApiDotNetCoreExample -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres
+docker run --rm --name jsonapi-dotnet-core-testing -e POSTGRES_DB=JsonApiDotNetCoreExample -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres
```
And then to run the tests:
@@ -107,3 +108,15 @@ Sometimes the compiled files can be dirty / corrupt from other branches / failed
```bash
dotnet clean
```
+
+
+## Compatibility
+
+A lot of changes were introduced in v4.0.0, the following chart should help you with compatibility issues between .NET Core versions
+
+| .NET Core Version | JADNC Version |
+| ----------------- | ------------- |
+| 2.0 - 2.2 | v3.* |
+| 3.* | v4.* |
+
+
diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj
index e29e94ce6a..9e2d0beb46 100644
--- a/src/Examples/GettingStarted/GettingStarted.csproj
+++ b/src/Examples/GettingStarted/GettingStarted.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.0
+ $(NetCoreAppVersion)
@@ -14,7 +14,6 @@
-
diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs
index f3e98948c8..75a4704301 100644
--- a/src/Examples/GettingStarted/Startup.cs
+++ b/src/Examples/GettingStarted/Startup.cs
@@ -1,5 +1,4 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using JsonApiDotNetCore.Extensions;
@@ -21,11 +20,9 @@ public void ConfigureServices(IServiceCollection services)
discover => discover.AddCurrentAssembly(), mvcBuilder: mvcBuilder);
}
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, SampleDbContext context)
+ public void Configure(IApplicationBuilder app, SampleDbContext context)
{
context.Database.EnsureDeleted(); // indicies need to be reset
- context.Database.EnsureCreated();
-
app.UseJsonApi();
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
index 6a6f7994fb..68b274b9d8 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
@@ -1,6 +1,5 @@
using JsonApiDotNetCoreExample.Models;
using Microsoft.EntityFrameworkCore;
-using JsonApiDotNetCoreExample.Models.Entities;
namespace JsonApiDotNetCoreExample.Data
{
@@ -25,19 +24,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.WithMany(p => p.TodoItems)
.HasForeignKey(t => t.OwnerId);
- modelBuilder.Entity()
- .HasKey(r => new { r.CourseId, r.StudentId });
-
- modelBuilder.Entity()
- .HasOne(r => r.Course)
- .WithMany(c => c.Students)
- .HasForeignKey(r => r.CourseId);
-
- modelBuilder.Entity()
- .HasOne(r => r.Student)
- .WithMany(s => s.Courses)
- .HasForeignKey(r => r.StudentId);
-
modelBuilder.Entity()
.HasKey(bc => new { bc.ArticleId, bc.TagId });
@@ -84,10 +70,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
public DbSet Authors { get; set; }
public DbSet NonJsonApiResources { get; set; }
public DbSet Users { get; set; }
- public DbSet Courses { get; set; }
- public DbSet Departments { get; set; }
- public DbSet Registrations { get; set; }
- public DbSet Students { get; set; }
public DbSet PersonRoles { get; set; }
public DbSet ArticleTags { get; set; }
public DbSet IdentifiableArticleTags { get; set; }
diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
index b3d9610ed5..d67f773ea7 100644
--- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
+++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj
@@ -13,7 +13,6 @@
-
@@ -29,4 +28,7 @@
+
+
+
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/CourseEntity.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Entities/CourseEntity.cs
deleted file mode 100644
index a5e2c45f52..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/CourseEntity.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using JsonApiDotNetCore.Models;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace JsonApiDotNetCoreExample.Models.Entities
-{
- [Table("Course")]
- public class CourseEntity : Identifiable
- {
- [Column("number")]
- [Required]
- public int Number { get; set; }
-
- [Column("title")]
- [Required]
- [StringLength(255)]
- public string Title { get; set; }
-
- [Column("description")]
- [StringLength(4000)]
- public string Description { get; set; }
-
- public DepartmentEntity Department { get; set; }
-
- [Column("department_id")]
- public int? DepartmentId { get; set; }
-
- public List Students { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/CourseStudentEntity.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Entities/CourseStudentEntity.cs
deleted file mode 100644
index 3fe23cdc67..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/CourseStudentEntity.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using JsonApiDotNetCore.Models;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace JsonApiDotNetCoreExample.Models.Entities
-{
- [Table("CourseStudent")]
- public class CourseStudentEntity : Identifiable
- {
- private CourseEntity _course;
- private StudentEntity _student;
- private ILazyLoader _loader { get; set; }
- private CourseStudentEntity(ILazyLoader loader)
- {
- _loader = loader;
- }
-
- public CourseStudentEntity(int courseId, int studentId)
- {
- CourseId = courseId;
- StudentId = studentId;
- }
-
- public CourseStudentEntity(CourseEntity course, StudentEntity student)
- {
- Course = course;
- CourseId = course.Id;
- Student = student;
- StudentId = student.Id;
- }
-
- [Column("course_id")]
- public int CourseId { get; set; }
-
- public CourseEntity Course
- {
- get => _loader.Load(this, ref _course);
- set => _course = value;
- }
-
- [Column("student_id")]
- public int StudentId { get; set; }
-
- public StudentEntity Student
- {
- get => _loader.Load(this, ref _student);
- set => _student = value;
- }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/DepartmentEntity.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Entities/DepartmentEntity.cs
deleted file mode 100644
index 337de4279f..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/DepartmentEntity.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using JsonApiDotNetCore.Models;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace JsonApiDotNetCoreExample.Models.Entities
-{
- [Table("Department")]
- public class DepartmentEntity : Identifiable
- {
- [Required]
- [StringLength(255, MinimumLength = 3)]
- public string Name { get; set; }
-
- public List Courses { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/StudentEntity.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Entities/StudentEntity.cs
deleted file mode 100644
index 1e23a471c5..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Entities/StudentEntity.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using JsonApiDotNetCore.Models;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace JsonApiDotNetCoreExample.Models.Entities
-{
- [Table("Student")]
- public class StudentEntity : Identifiable
- {
- [Column("firstname")]
- [Required]
- public string FirstName { get; set; }
-
- [Column("lastname")]
- [Required]
- [StringLength(255, MinimumLength = 3)]
- public string LastName { get; set; }
-
- [Column("address")]
- public string Address { get; set; }
-
- public List Courses { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseResource.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseResource.cs
deleted file mode 100644
index e981c70cc9..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseResource.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using JsonApiDotNetCore.Models;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using JsonApiDotNetCoreExample.Models.Entities;
-
-namespace JsonApiDotNetCoreExample.Models.Resources
-{
- public class CourseResource : Identifiable
- {
- [Attr("number")]
- [Required]
- public int Number { get; set; }
-
- [Attr("title")]
- [Required]
- public string Title { get; set; }
-
- [Attr("description")]
- public string Description { get; set; }
-
- [HasOne("department", mappedBy: "Department")]
- public DepartmentResource Department { get; set; }
- public int? DepartmentId { get; set; }
-
- [HasMany("students")]
- public List Students { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseStudentResource.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseStudentResource.cs
deleted file mode 100644
index c6a6619ab8..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseStudentResource.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using JsonApiDotNetCore.Models;
-
-namespace JsonApiDotNetCoreExample.Models.Resources
-{
- ///
- /// Note: EF Core *requires* the creation of an additional entity
- /// for many to many relationships and no longer implicitly creates
- /// it. While it may not make sense to create a corresponding "resource"
- /// for that relationship, due to the need to make the underlying
- /// framework and mapping understand the explicit navigation entity,
- /// a mirroring DTO resource is also required.
- ///
- public class CourseStudentResource : Identifiable
- {
- [HasOne("course")]
- public CourseResource Course { get; set; }
- public int CourseId { get; set; }
-
- [HasOne("student")]
- public StudentResource Student { get; set; }
- public int StudentId { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/DepartmentResource.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Resources/DepartmentResource.cs
deleted file mode 100644
index 47648afcdf..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/DepartmentResource.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using JsonApiDotNetCore.Models;
-using System.Collections.Generic;
-
-namespace JsonApiDotNetCoreExample.Models.Resources
-{
- public class DepartmentResource : Identifiable
- {
- [Attr("name")]
- public string Name { get; set; }
-
- [HasMany("courses", mappedBy: "Courses")]
- public List Courses { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/StudentResource.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Resources/StudentResource.cs
deleted file mode 100644
index 086d81614b..0000000000
--- a/src/Examples/JsonApiDotNetCoreExample/Models/Resources/StudentResource.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using JsonApiDotNetCore.Models;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-
-namespace JsonApiDotNetCoreExample.Models.Resources
-{
- public class StudentResource : Identifiable
- {
- [Attr("firstname")]
- [Required]
- public string FirstName { get; set; }
-
- [Attr("lastname")]
- [Required]
- public string LastName { get; set; }
-
- [Attr("address")]
- public string Address { get; set; }
-
- [HasMany("courses")]
- public List Courses { get; set; }
- }
-}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs
index da8fc957b7..4c786b238c 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs
@@ -31,7 +31,7 @@ public Dictionary GetMeta()
{
return new Dictionary {
{ "copyright", "Copyright 2015 Example Corp." },
- { "authors", new string[] { "Jared Nance", "Maurits Moeys" } }
+ { "authors", new string[] { "Jared Nance", "Maurits Moeys", "Harro van der Kroft" } }
};
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs
index a8d6f039e4..9aa8d8397f 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs
@@ -26,10 +26,12 @@ private IQueryable FirstCharacterFilter(IQueryable users, FilterQuer
{
switch (filterQuery.Operation)
{
+ /// In EF core >= 3.0 we need to explicitly evaluate the query first. This could probably be translated
+ /// into a query by building expression trees.
case "lt":
- return users.Where(u => u.Username[0] < filterQuery.Value[0]);
+ return users.ToList().Where(u => u.Username.First() < filterQuery.Value[0]).AsQueryable();
default:
- return users.Where(u => u.Username[0] == filterQuery.Value[0]);
+ return users.ToList().Where(u => u.Username.First() == filterQuery.Value[0]).AsQueryable();
}
}
}
diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/ClientGeneratedIdsStartup.cs
similarity index 77%
rename from test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs
rename to src/Examples/JsonApiDotNetCoreExample/Startups/ClientGeneratedIdsStartup.cs
index 64147e5460..10255d6727 100644
--- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Startups/ClientGeneratedIdsStartup.cs
@@ -4,19 +4,21 @@
using JsonApiDotNetCoreExample.Data;
using Microsoft.EntityFrameworkCore;
using JsonApiDotNetCore.Extensions;
-using System;
-using JsonApiDotNetCoreExample;
using System.Reflection;
-namespace JsonApiDotNetCoreExampleTests.Startups
+namespace JsonApiDotNetCoreExample
{
+ ///
+ /// This should be in JsonApiDotNetCoreExampleTests project but changes in .net core 3.0
+ /// do no longer allow that. See https://github.com/aspnet/AspNetCore/issues/15373.
+ ///
public class ClientGeneratedIdsStartup : Startup
{
- public ClientGeneratedIdsStartup(IHostingEnvironment env)
+ public ClientGeneratedIdsStartup(IWebHostEnvironment env)
: base (env)
{ }
- public override IServiceProvider ConfigureServices(IServiceCollection services)
+ public override void ConfigureServices(IServiceCollection services)
{
var loggerFactory = new LoggerFactory();
var mvcBuilder = services.AddMvcCore();
@@ -37,9 +39,6 @@ public override IServiceProvider ConfigureServices(IServiceCollection services)
},
discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))),
mvcBuilder: mvcBuilder);
-
- return services.BuildServiceProvider();
-
}
}
}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs
new file mode 100644
index 0000000000..32accb087a
--- /dev/null
+++ b/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs
@@ -0,0 +1,34 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using JsonApiDotNetCore.Services;
+using System.Collections.Generic;
+
+namespace JsonApiDotNetCoreExample
+{
+ ///
+ /// This should be in JsonApiDotNetCoreExampleTests project but changes in .net core 3.0
+ /// do no longer allow that. See https://github.com/aspnet/AspNetCore/issues/15373.
+ ///
+ public class MetaStartup : Startup
+ {
+ public MetaStartup(IWebHostEnvironment env)
+ : base (env)
+ { }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddScoped();
+ base.ConfigureServices(services);
+ }
+ }
+
+ public class MetaService : IRequestMeta
+ {
+ public Dictionary GetMeta()
+ {
+ return new Dictionary {
+ { "request-meta", "request-meta-value" }
+ };
+ }
+ }
+}
diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs
similarity index 90%
rename from src/Examples/JsonApiDotNetCoreExample/Startup.cs
rename to src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs
index 4463ca98da..ba7404cc04 100644
--- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs
+++ b/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs
@@ -14,18 +14,17 @@ public class Startup
{
public readonly IConfiguration Config;
- public Startup(IHostingEnvironment env)
+ public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
-
Config = builder.Build();
}
- public virtual IServiceProvider ConfigureServices(IServiceCollection services)
+ public virtual void ConfigureServices(IServiceCollection services)
{
var loggerFactory = new LoggerFactory();
services
@@ -45,15 +44,14 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
options.LoaDatabaseValues = true;
},
discovery => discovery.AddCurrentAssembly());
- return services.BuildServiceProvider();
}
public virtual void Configure(
IApplicationBuilder app,
- IHostingEnvironment env,
ILoggerFactory loggerFactory,
AppDbContext context)
{
+
context.Database.EnsureCreated();
app.UseJsonApi();
}
diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/TodoItemsController.cs
similarity index 73%
rename from src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs
rename to src/Examples/NoEntityFrameworkExample/Controllers/TodoItemsController.cs
index 9a593cfa9f..cf18987700 100644
--- a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs
+++ b/src/Examples/NoEntityFrameworkExample/Controllers/TodoItemsController.cs
@@ -1,14 +1,14 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
-using JsonApiDotNetCoreExample.Models;
+using NoEntityFrameworkExample.Models;
using Microsoft.Extensions.Logging;
namespace NoEntityFrameworkExample.Controllers
{
- public class CustomTodoItemsController : JsonApiController
+ public class TodoItemsController : JsonApiController
{
- public CustomTodoItemsController(
+ public TodoItemsController(
IJsonApiOptions jsonApiOptions,
IResourceService resourceService,
ILoggerFactory loggerFactory)
diff --git a/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs b/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs
new file mode 100644
index 0000000000..e7247108dd
--- /dev/null
+++ b/src/Examples/NoEntityFrameworkExample/Data/AppDbContext.cs
@@ -0,0 +1,14 @@
+using NoEntityFrameworkExample.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace NoEntityFrameworkExample.Data
+{
+ public class AppDbContext : DbContext
+ {
+ public AppDbContext(DbContextOptions options)
+ : base(options)
+ { }
+
+ public DbSet TodoItems { get; set; }
+ }
+}
diff --git a/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs b/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs
new file mode 100644
index 0000000000..b1021d18f5
--- /dev/null
+++ b/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs
@@ -0,0 +1,36 @@
+using System;
+using JsonApiDotNetCore.Models;
+
+namespace NoEntityFrameworkExample.Models
+{
+ public class TodoItem : Identifiable
+ {
+ public TodoItem()
+ {
+ GuidProperty = Guid.NewGuid();
+ }
+
+ public bool IsLocked { get; set; }
+
+ [Attr("description")]
+ public string Description { get; set; }
+
+ [Attr("ordinal")]
+ public long Ordinal { get; set; }
+
+ [Attr("guid-property")]
+ public Guid GuidProperty { get; set; }
+
+ [Attr("created-date")]
+ public DateTime CreatedDate { get; set; }
+
+ [Attr("achieved-date", isFilterable: false, isSortable: false)]
+ public DateTime? AchievedDate { get; set; }
+
+ [Attr("updated-date")]
+ public DateTime? UpdatedDate { get; set; }
+
+ [Attr("offset-date")]
+ public DateTimeOffset? OffsetDate { get; set; }
+ }
+}
diff --git a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj
old mode 100755
new mode 100644
index 86825c5621..b387f93746
--- a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj
+++ b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj
@@ -3,23 +3,13 @@
$(NetCoreAppVersion)
InProcess
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs
index b99c99c85a..09078cda2c 100644
--- a/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs
+++ b/src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Configuration;
using Npgsql;
using Dapper;
using System.Data;
-using JsonApiDotNetCoreExample.Models;
+using NoEntityFrameworkExample.Models;
using System.Linq;
namespace NoEntityFrameworkExample.Services
@@ -20,7 +19,7 @@ public TodoItemService(IConfiguration config)
{
_connectionString = config.GetValue("Data:DefaultConnection");
}
-
+
private IDbConnection Connection
{
get
diff --git a/src/Examples/NoEntityFrameworkExample/Startup.cs b/src/Examples/NoEntityFrameworkExample/Startup.cs
index a438ff9336..1d6aef07c5 100644
--- a/src/Examples/NoEntityFrameworkExample/Startup.cs
+++ b/src/Examples/NoEntityFrameworkExample/Startup.cs
@@ -1,7 +1,5 @@
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Services;
-using JsonApiDotNetCoreExample.Data;
-using JsonApiDotNetCoreExample.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
@@ -9,13 +7,14 @@
using Microsoft.Extensions.Logging;
using NoEntityFrameworkExample.Services;
using Microsoft.EntityFrameworkCore;
-using System;
+using NoEntityFrameworkExample.Data;
+using NoEntityFrameworkExample.Models;
namespace NoEntityFrameworkExample
{
public class Startup
{
- public Startup(IHostingEnvironment env)
+ public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
@@ -28,7 +27,7 @@ public Startup(IHostingEnvironment env)
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
- public virtual IServiceProvider ConfigureServices(IServiceCollection services)
+ public virtual void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var mvcBuilder = services.AddMvcCore();
@@ -37,17 +36,16 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
builder.AddConfiguration(Configuration.GetSection("Logging"));
builder.AddConsole();
}).AddJsonApi(
- options => options.Namespace = "api/v1",
- resources: resources => resources.AddResource("custom-todo-items"),
+ options => options.Namespace = "api/v1",
+ resources: resources => resources.AddResource("todo-items"),
mvcBuilder: mvcBuilder
);
services.AddScoped, TodoItemService>();
var optionsBuilder = new DbContextOptionsBuilder();
- optionsBuilder.UseNpgsql(Configuration.GetValue("Data:DefaultConnection"));
+ optionsBuilder.UseNpgsql(GetDbConnectionString());
services.AddSingleton(Configuration);
services.AddSingleton(optionsBuilder.Options);
services.AddScoped();
- return services.BuildServiceProvider();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -56,5 +54,7 @@ public void Configure(IApplicationBuilder app, AppDbContext context)
context.Database.EnsureCreated();
app.UseJsonApi();
}
+
+ public string GetDbConnectionString() => Configuration["Data:DefaultConnection"];
}
}
diff --git a/src/Examples/NoEntityFrameworkExample/appsettings.json b/src/Examples/NoEntityFrameworkExample/appsettings.json
old mode 100755
new mode 100644
index 42da2105cc..ed7d5999d7
--- a/src/Examples/NoEntityFrameworkExample/appsettings.json
+++ b/src/Examples/NoEntityFrameworkExample/appsettings.json
@@ -1,11 +1,11 @@
{
- "Data": {
- "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password="
- },
- "Logging": {
- "IncludeScopes": false,
- "LogLevel": {
- "Default": "Warning"
+ "Data": {
+ "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=postgres"
+ },
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Warning"
+ }
}
- }
}
diff --git a/src/Examples/ReportsExample/ReportsExample.csproj b/src/Examples/ReportsExample/ReportsExample.csproj
index 50e64df8f6..ee832bdf7a 100644
--- a/src/Examples/ReportsExample/ReportsExample.csproj
+++ b/src/Examples/ReportsExample/ReportsExample.csproj
@@ -13,8 +13,6 @@
-
-
diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs
index 609847fa04..480f2a0f62 100644
--- a/src/Examples/ReportsExample/Startup.cs
+++ b/src/Examples/ReportsExample/Startup.cs
@@ -11,7 +11,7 @@ public class Startup
{
public readonly IConfiguration Config;
- public Startup(IHostingEnvironment env)
+ public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
@@ -29,10 +29,5 @@ public virtual void ConfigureServices(IServiceCollection services)
opt => opt.Namespace = "api",
discovery => discovery.AddCurrentAssembly(), mvcBuilder: mvcBuilder);
}
-
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
- {
- app.UseMvc();
- }
}
}
diff --git a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
index 7942e3774c..302aeb831f 100644
--- a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
@@ -42,6 +42,11 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv
_mvcBuilder = mvcBuilder;
}
+ internal void ConfigureLogging()
+ {
+ _services.AddLogging();
+ }
+
///
/// Executes the action provided by the user to configure
///
@@ -62,17 +67,17 @@ public void ConfigureMvc()
_serviceDiscoveryFacade = intermediateProvider.GetRequiredService();
var exceptionFilterProvider = intermediateProvider.GetRequiredService();
var typeMatchFilterProvider = intermediateProvider.GetRequiredService();
+ var routingConvention = intermediateProvider.GetRequiredService();
- _mvcBuilder.AddMvcOptions(mvcOptions =>
+ _mvcBuilder.AddMvcOptions(options =>
{
- mvcOptions.Filters.Add(exceptionFilterProvider.Get());
- mvcOptions.Filters.Add(typeMatchFilterProvider.Get());
- mvcOptions.InputFormatters.Insert(0, new JsonApiInputFormatter());
- mvcOptions.OutputFormatters.Insert(0, new JsonApiOutputFormatter());
+ options.EnableEndpointRouting = true;
+ options.Filters.Add(exceptionFilterProvider.Get());
+ options.Filters.Add(typeMatchFilterProvider.Get());
+ options.InputFormatters.Insert(0, new JsonApiInputFormatter());
+ options.OutputFormatters.Insert(0, new JsonApiOutputFormatter());
+ options.Conventions.Insert(0, routingConvention);
});
-
- var routingConvention = intermediateProvider.GetRequiredService();
- _mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, routingConvention));
_services.AddSingleton(routingConvention);
}
@@ -148,14 +153,14 @@ public void ConfigureServices()
_services.AddSingleton(JsonApiOptions);
_services.AddSingleton(resourceGraph);
_services.AddSingleton();
- _services.AddSingleton(resourceGraph);
+ _services.AddSingleton(resourceGraph);
_services.AddSingleton(resourceGraph);
_services.AddScoped();
_services.AddScoped();
_services.AddScoped();
_services.AddScoped();
_services.AddScoped();
- _services.AddScoped(typeof(HasManyThroughUpdateHelper<>));
+ _services.AddScoped(typeof(RepositoryRelationshipUpdateHelper<>));
_services.AddScoped();
_services.AddScoped();
_services.AddScoped();
diff --git a/src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs b/src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs
index 12eba8f6d0..83f0a3fd28 100644
--- a/src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs
+++ b/src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs
@@ -10,6 +10,7 @@
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Serialization;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.Extensions.Logging;
namespace JsonApiDotNetCore.Data
@@ -152,25 +153,29 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type)
private void DetachRelationships(TResource entity)
{
- foreach (var relationshipAttr in _targetedFields.Relationships)
+ foreach (var relationship in _targetedFields.Relationships)
{
- if (relationshipAttr is HasOneAttribute hasOneAttr)
- {
- var relationshipValue = (IIdentifiable)hasOneAttr.GetValue(entity);
- if (relationshipValue == null) continue;
- _context.Entry(relationshipValue).State = EntityState.Detached;
- }
- else
+ var value = relationship.GetValue(entity);
+ if (value == null)
+ continue;
+
+ if (value is IEnumerable collection)
{
- IEnumerable relationshipValueList = (IEnumerable)relationshipAttr.GetValue(entity);
- if (relationshipValueList == null) continue;
- foreach (var pointer in relationshipValueList)
- _context.Entry(pointer).State = EntityState.Detached;
+ foreach (IIdentifiable single in collection.ToList())
+ _context.Entry(single).State = EntityState.Detached;
/// detaching has many relationships is not sufficient to
/// trigger a full reload of relationships: the navigation
/// property actually needs to be nulled out, otherwise
/// EF will still add duplicate instances to the collection
- relationshipAttr.SetValue(entity, null);
+ relationship.SetValue(entity, null);
+ }
+ else
+ {
+ _context.Entry(value).State = EntityState.Detached;
+
+ /// temporary work around for https://github.com/aspnet/EntityFrameworkCore/issues/18621
+ /// as soon as ef core 3.1 lands we can get rid of this again.
+ _context.Entry(entity).State = EntityState.Detached;
}
}
}
@@ -236,11 +241,13 @@ private IList GetTrackedManyRelationshipValue(IEnumerable relatio
if (relationshipValueList == null) return null;
bool _wasAlreadyAttached = false;
var trackedPointerCollection = relationshipValueList.Select(pointer =>
- { // convert each element in the value list to relationshipAttr.DependentType.
- var tracked = AttachOrGetTracked(pointer);
- if (tracked != null) _wasAlreadyAttached = true;
- return Convert.ChangeType(tracked ?? pointer, relationshipAttr.RightType);
- }).ToList().Cast(relationshipAttr.RightType);
+ { // convert each element in the value list to relationshipAttr.DependentType.
+ var tracked = AttachOrGetTracked(pointer);
+ if (tracked != null) _wasAlreadyAttached = true;
+ return Convert.ChangeType(tracked ?? pointer, relationshipAttr.RightType);
+ })
+ .ToList()
+ .Cast(relationshipAttr.RightType);
if (_wasAlreadyAttached) wasAlreadyAttached = true;
return (IList)trackedPointerCollection;
}
@@ -256,19 +263,13 @@ private IIdentifiable GetTrackedHasOneRelationshipValue(IIdentifiable relationsh
///
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds)
{
- if (relationship is HasManyThroughAttribute hasManyThrough)
- {
- var helper = _genericServiceFactory.Get(typeof(HasManyThroughUpdateHelper<>), hasManyThrough.ThroughType);
- await helper.UpdateAsync((IIdentifiable)parent, hasManyThrough, relationshipIds);
- return;
- }
+ var typeToUpdate = (relationship is HasManyThroughAttribute hasManyThrough)
+ ? hasManyThrough.ThroughType
+ : relationship.RightType;
- var context = _context.Set(relationship.RightType);
- var updatedValue = relationship is HasManyAttribute
- ? context.Where(e => relationshipIds.Contains(((IIdentifiable)e).StringId)).Cast(relationship.RightType)
- : context.FirstOrDefault(e => relationshipIds.First() == ((IIdentifiable)e).StringId);
+ var helper = _genericServiceFactory.Get(typeof(RepositoryRelationshipUpdateHelper<>), typeToUpdate);
+ await helper.UpdateRelationshipAsync((IIdentifiable)parent, relationship, relationshipIds);
- relationship.SetValue(parent, updatedValue);
await _context.SaveChangesAsync();
}
@@ -285,7 +286,9 @@ public virtual async Task DeleteAsync(TId id)
public virtual IQueryable Include(IQueryable entities, IEnumerable inclusionChain = null)
{
if (inclusionChain == null || !inclusionChain.Any())
+ {
return entities;
+ }
string internalRelationshipPath = null;
foreach (var relationship in inclusionChain)
@@ -299,31 +302,38 @@ public virtual IQueryable Include(IQueryable entities, IEn
///
public virtual async Task> PageAsync(IQueryable entities, int pageSize, int pageNumber)
{
+ // the IQueryable returned from the hook executor is sometimes consumed here.
+ // In this case, it does not support .ToListAsync(), so we use the method below.
if (pageNumber >= 0)
{
- // the IQueryable returned from the hook executor is sometimes consumed here.
- // In this case, it does not support .ToListAsync(), so we use the method below.
- return await this.ToListAsync(entities.PageForward(pageSize, pageNumber));
+ entities = entities.PageForward(pageSize, pageNumber);
+ return entities is IAsyncQueryProvider ? await entities.ToListAsync() : entities.ToList();
}
+ if (entities is IAsyncEnumerable)
+ {
+ // since EntityFramework does not support IQueryable.Reverse(), we need to know the number of queried entities
+ var totalCount = await entities.CountAsync();
- // since EntityFramework does not support IQueryable.Reverse(), we need to know the number of queried entities
- int numberOfEntities = await this.CountAsync(entities);
-
- // may be negative
- int virtualFirstIndex = numberOfEntities - pageSize * Math.Abs(pageNumber);
- int numberOfElementsInPage = Math.Min(pageSize, virtualFirstIndex + pageSize);
+ int virtualFirstIndex = totalCount - pageSize * Math.Abs(pageNumber);
+ int numberOfElementsInPage = Math.Min(pageSize, virtualFirstIndex + pageSize);
- return await ToListAsync(entities
- .Skip(virtualFirstIndex)
- .Take(numberOfElementsInPage));
+ return await ToListAsync(entities.Skip(virtualFirstIndex).Take(numberOfElementsInPage));
+ } else
+ {
+ int firstIndex = pageSize * Math.Abs(pageNumber) - 1;
+ int numberOfElementsInPage = Math.Min(pageSize, firstIndex + pageSize);
+ return entities.Reverse().Skip(firstIndex).Take(numberOfElementsInPage);
+ }
}
///
public async Task CountAsync(IQueryable entities)
{
- return (entities is IAsyncEnumerable)
- ? await entities.CountAsync()
- : entities.Count();
+ if (entities is IAsyncEnumerable)
+ {
+ return await entities.CountAsync();
+ }
+ return entities.Count();
}
///
@@ -337,9 +347,11 @@ public async Task FirstOrDefaultAsync(IQueryable entities)
///
public async Task> ToListAsync(IQueryable entities)
{
- return (entities is IAsyncEnumerable)
- ? await entities.ToListAsync()
- : entities.ToList();
+ if (entities is IAsyncEnumerable)
+ {
+ return await entities.ToListAsync();
+ }
+ return entities.ToList();
}
///
diff --git a/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs b/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs
index ebe89815f8..31ba185125 100644
--- a/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs
+++ b/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using JsonApiDotNetCore.Models;
using Microsoft.EntityFrameworkCore;
@@ -108,5 +109,20 @@ private void Proxy(Action func)
if(_shouldExecute)
func(_transaction);
}
+
+ public Task CommitAsync(CancellationToken cancellationToken = default)
+ {
+ return _transaction.CommitAsync(cancellationToken);
+ }
+
+ public Task RollbackAsync(CancellationToken cancellationToken = default)
+ {
+ return _transaction.RollbackAsync(cancellationToken);
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return _transaction.DisposeAsync();
+ }
}
}
diff --git a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs
index e7cbcce43d..5f4aeb53dd 100644
--- a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs
+++ b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs
@@ -5,7 +5,6 @@
using JsonApiDotNetCore.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -14,32 +13,35 @@ namespace JsonApiDotNetCore.Extensions
// ReSharper disable once InconsistentNaming
public static class IApplicationBuilderExtensions
{
- public static IApplicationBuilder UseJsonApi(this IApplicationBuilder app, bool useMvc = true)
+ ///
+ /// Adds necessary components such as routing to your application
+ ///
+ ///
+ ///
+ public static void UseJsonApi(this IApplicationBuilder app)
{
DisableDetailedErrorsIfProduction(app);
LogResourceGraphValidations(app);
-
- app.UseEndpointRouting();
-
- app.UseMiddleware();
-
- if (useMvc)
- app.UseMvc();
-
using (var scope = app.ApplicationServices.CreateScope())
{
var inverseRelationshipResolver = scope.ServiceProvider.GetService();
inverseRelationshipResolver?.Resolve();
}
- return app;
+ // An endpoint is selected and set on the HttpContext if a match is found
+ app.UseRouting();
+
+ // middleware to run after routing occurs.
+ app.UseMiddleware();
+
+ // Executes the endpoints that was selected by routing.
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
}
private static void DisableDetailedErrorsIfProduction(IApplicationBuilder app)
{
- var environment = (IHostingEnvironment)app.ApplicationServices.GetService(typeof(IHostingEnvironment));
-
- if (environment.IsProduction())
+ var webHostEnvironment = (IWebHostEnvironment) app.ApplicationServices.GetService(typeof(IWebHostEnvironment));
+ if (webHostEnvironment.EnvironmentName == "Production")
{
JsonApiOptions.DisableErrorStackTraces = true;
JsonApiOptions.DisableErrorSource = true;
diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
index 5e37f416cb..75b9f4cda4 100644
--- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
@@ -35,6 +35,7 @@ public static IServiceCollection AddJsonApi(this IServiceColle
var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
if (options != null)
application.ConfigureJsonApiOptions(options);
+ application.ConfigureLogging();
application.ConfigureMvc();
application.ConfigureResources(resources);
application.ConfigureServices();
diff --git a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs
index 3bbfbbb09d..262e81678e 100644
--- a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs
+++ b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs
@@ -1,9 +1,9 @@
using System;
+using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
-using Microsoft.EntityFrameworkCore.Internal;
namespace JsonApiDotNetCore.Extensions
{
@@ -15,7 +15,9 @@ public static ErrorCollection ConvertToErrorCollection(this ModelStateDiction
foreach (var entry in modelState)
{
if (entry.Value.Errors.Any() == false)
+ {
continue;
+ }
var targetedProperty = resourceType.GetProperty(entry.Key);
var attrName = targetedProperty.GetCustomAttribute().PublicAttributeName;
@@ -23,16 +25,21 @@ public static ErrorCollection ConvertToErrorCollection(this ModelStateDiction
foreach (var modelError in entry.Value.Errors)
{
if (modelError.Exception is JsonApiException jex)
+ {
collection.Errors.AddRange(jex.GetError().Errors);
+ }
else
+ {
collection.Errors.Add(new Error(
status: 422,
title: entry.Key,
detail: modelError.ErrorMessage,
meta: modelError.Exception != null ? ErrorMeta.FromException(modelError.Exception) : null,
- source: attrName == null ? null : new {
+ source: attrName == null ? null : new
+ {
pointer = $"/data/attributes/{attrName}"
- }));
+ }));
+ }
}
}
return collection;
diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs
index b456932fc5..6facec6a6a 100644
--- a/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs
+++ b/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs
@@ -17,7 +17,6 @@ public bool CanWriteResult(OutputFormatterCanWriteContext context)
return string.IsNullOrEmpty(contentTypeString) || contentTypeString == Constants.ContentType;
}
-
public async Task WriteAsync(OutputFormatterWriteContext context)
{
var writer = context.HttpContext.RequestServices.GetService();
diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs
index d00f47526e..69d04ed3cc 100644
--- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs
+++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs
@@ -23,26 +23,25 @@ public JsonApiReader(IJsonApiDeserializer deserializer,
_logger = loggerFactory.CreateLogger();
}
- public Task ReadAsync(InputFormatterContext context)
+ public async Task ReadAsync(InputFormatterContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var request = context.HttpContext.Request;
if (request.ContentLength == 0)
- return InputFormatterResult.SuccessAsync(null);
+ {
+ return await InputFormatterResult.SuccessAsync(null);
+ }
try
{
- var body = GetRequestBody(context.HttpContext.Request.Body);
-
+ var body = await GetRequestBody(context.HttpContext.Request.Body);
object model = _deserializer.Deserialize(body);
-
if (model == null)
{
_logger?.LogError("An error occurred while de-serializing the payload");
}
-
if (context.HttpContext.Request.Method == "PATCH")
{
bool idMissing;
@@ -60,13 +59,13 @@ public Task ReadAsync(InputFormatterContext context)
throw new JsonApiException(400, "Payload must include id attribute");
}
}
- return InputFormatterResult.SuccessAsync(model);
+ return await InputFormatterResult.SuccessAsync(model);
}
catch (Exception ex)
{
_logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload");
context.ModelState.AddModelError(context.ModelName, ex, context.Metadata);
- return InputFormatterResult.FailureAsync();
+ return await InputFormatterResult.FailureAsync();
}
}
@@ -103,11 +102,19 @@ private bool CheckForId(IList modelList)
return false;
}
- private string GetRequestBody(Stream body)
+ ///
+ /// Fetches the request from body asynchronously.
+ ///
+ /// Input stream for body
+ /// String content of body sent to server.
+ private async Task GetRequestBody(Stream body)
{
using (var reader = new StreamReader(body))
{
- return reader.ReadToEnd();
+ // This needs to be set to async because
+ // Synchronous IO operations are
+ // https://github.com/aspnet/AspNetCore/issues/7644
+ return await reader.ReadToEndAsync();
}
}
}
diff --git a/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs b/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs
index cf25305ca8..eb68142e31 100644
--- a/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs
+++ b/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs
@@ -73,7 +73,7 @@ public void Apply(ApplicationModel application)
}
///
- /// verifies if routing convention should be enabled for this controller
+ /// Verifies if routing convention should be enabled for this controller
///
private bool RoutingConventionDisabled(ControllerModel controller)
{
@@ -90,9 +90,10 @@ private string TemplateFromResource(ControllerModel model)
if (_registeredResources.TryGetValue(model.ControllerName, out Type resourceType))
{
var template = $"{_namespace}/{_formatter.FormatResourceName(resourceType)}";
- if (_registeredTemplates.Add(template))
+ if (_registeredTemplates.Add(template))
+ {
return template;
-
+ }
}
return null;
}
@@ -104,8 +105,13 @@ private string TemplateFromController(ControllerModel model)
{
var template = $"{_namespace}/{_formatter.ApplyCasingConvention(model.ControllerName)}";
if (_registeredTemplates.Add(template))
+ {
return template;
- return null;
+ }
+ else
+ {
+ return null;
+ }
}
///
@@ -126,12 +132,16 @@ private Type GetResourceTypeFromController(Type type)
{
var potentialResource = currentBaseType.GetGenericArguments().FirstOrDefault(t => t.Inherits(identifiable));
if (potentialResource != null)
+ {
return potentialResource;
+ }
}
currentBaseType = nextBaseType;
if (nextBaseType == null)
+ {
break;
+ }
}
return currentBaseType?.GetGenericArguments().First();
}
diff --git a/src/JsonApiDotNetCore/Internal/Generics/HasManyThroughUpdateHelper.cs b/src/JsonApiDotNetCore/Internal/Generics/HasManyThroughUpdateHelper.cs
deleted file mode 100644
index c98560015e..0000000000
--- a/src/JsonApiDotNetCore/Internal/Generics/HasManyThroughUpdateHelper.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading.Tasks;
-using JsonApiDotNetCore.Data;
-using JsonApiDotNetCore.Extensions;
-using JsonApiDotNetCore.Models;
-using Microsoft.EntityFrameworkCore;
-
-namespace JsonApiDotNetCore.Internal.Generics
-{
- ///
- /// A special helper service that gets instantiated for the right-type of a many-to-many relationship and is responsible for
- /// processing updates for that relationships.
- ///
- public interface IHasManyThroughUpdateHelper
- {
- ///
- /// Processes updates of has many through relationship.
- ///
- Task UpdateAsync(IIdentifiable parent, HasManyThroughAttribute relationship, IEnumerable relationshipIds);
- }
-
- ///
- public class HasManyThroughUpdateHelper : IHasManyThroughUpdateHelper where T : class
- {
- private readonly DbContext _context;
- public HasManyThroughUpdateHelper(IDbContextResolver contextResolver)
- {
- _context = contextResolver.GetContext();
- }
-
- ///
- public virtual async Task UpdateAsync(IIdentifiable parent, HasManyThroughAttribute relationship, IEnumerable relationshipIds)
- {
- // we need to create a transaction for the HasManyThrough case so we can get and remove any existing
- // join entities and only commit if all operations are successful
- using (var transaction = await _context.GetCurrentOrCreateTransactionAsync())
- {
- // ArticleTag
- ParameterExpression parameter = Expression.Parameter(relationship.ThroughType);
-
- // ArticleTag.ArticleId
- Expression property = Expression.Property(parameter, relationship.LeftIdProperty);
-
- // article.Id
- var parentId = TypeHelper.ConvertType(parent.StringId, relationship.LeftIdProperty.PropertyType);
- Expression target = Expression.Constant(parentId);
-
- // ArticleTag.ArticleId.Equals(article.Id)
- Expression equals = Expression.Call(property, "Equals", null, target);
-
- var lambda = Expression.Lambda>(equals, parameter);
-
- // TODO: we shouldn't need to do this instead we should try updating the existing?
- // the challenge here is if a composite key is used, then we will fail to
- // create due to a unique key violation
- var oldLinks = _context
- .Set()
- .Where(lambda.Compile())
- .ToList();
-
- _context.RemoveRange(oldLinks);
-
- var newLinks = relationshipIds.Select(x => {
- var link = Activator.CreateInstance(relationship.ThroughType);
- relationship.LeftIdProperty.SetValue(link, TypeHelper.ConvertType(parentId, relationship.LeftIdProperty.PropertyType));
- relationship.RightIdProperty.SetValue(link, TypeHelper.ConvertType(x, relationship.RightIdProperty.PropertyType));
- return link;
- });
-
- _context.AddRange(newLinks);
- await _context.SaveChangesAsync();
-
- transaction.Commit();
- }
- }
- }
-}
diff --git a/src/JsonApiDotNetCore/Internal/Generics/RepositoryRelationshipUpdateHelper.cs b/src/JsonApiDotNetCore/Internal/Generics/RepositoryRelationshipUpdateHelper.cs
new file mode 100644
index 0000000000..ec4a4f0bbb
--- /dev/null
+++ b/src/JsonApiDotNetCore/Internal/Generics/RepositoryRelationshipUpdateHelper.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using JsonApiDotNetCore.Data;
+using JsonApiDotNetCore.Extensions;
+using JsonApiDotNetCore.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace JsonApiDotNetCore.Internal.Generics
+{
+ ///
+ /// A special helper that processes updates of relationships
+ ///
+ ///
+ /// This service required to be able translate involved expressions into queries
+ /// instead of having them evaluated on the client side. In particular, for all three types of relationship
+ /// a lookup is performed based on an id. Expressions that use IIdentifiable.StringId can never
+ /// be translated into queries because this property only exists at runtime after the query is performed.
+ /// We will have to build expression trees if we want to use IIdentifiable{TId}.TId, for which we minimally a
+ /// generic execution to DbContext.Set{T}().
+ ///
+ public interface IRepositoryRelationshipUpdateHelper
+ {
+ ///
+ /// Processes updates of relationships
+ ///
+ Task UpdateRelationshipAsync(IIdentifiable parent, RelationshipAttribute relationship, IEnumerable relationshipIds);
+ }
+
+ ///
+ public class RepositoryRelationshipUpdateHelper : IRepositoryRelationshipUpdateHelper where TRelatedResource : class
+ {
+ private readonly DbContext _context;
+ public RepositoryRelationshipUpdateHelper(IDbContextResolver contextResolver)
+ {
+ _context = contextResolver.GetContext();
+ }
+
+ ///
+ public virtual async Task UpdateRelationshipAsync(IIdentifiable parent, RelationshipAttribute relationship, IEnumerable relationshipIds)
+ {
+ if (relationship is HasManyThroughAttribute hasManyThrough)
+ await UpdateManyToManyAsync(parent, hasManyThrough, relationshipIds);
+ else if (relationship is HasManyAttribute)
+ await UpdateOneToManyAsync(parent, relationship, relationshipIds);
+ else
+ await UpdateOneToOneAsync(parent, relationship, relationshipIds);
+ }
+
+ private async Task UpdateOneToOneAsync(IIdentifiable parent, RelationshipAttribute relationship, IEnumerable relationshipIds)
+ {
+ TRelatedResource value = null;
+ if (relationshipIds.Any())
+ { // newOwner.id
+ var target = Expression.Constant(TypeHelper.ConvertType(relationshipIds.First(), TypeHelper.GetIdentifierType(relationship.RightType)));
+ // (Person p) => ...
+ ParameterExpression parameter = Expression.Parameter(typeof(TRelatedResource));
+ // (Person p) => p.Id
+ Expression idMember = Expression.Property(parameter, nameof(Identifiable.Id));
+ // newOwner.Id.Equals(p.Id)
+ Expression callEquals = Expression.Call(idMember, nameof(object.Equals), null, target);
+ var equalsLambda = Expression.Lambda>(callEquals, parameter);
+ value = await _context.Set().FirstOrDefaultAsync(equalsLambda);
+ }
+ relationship.SetValue(parent, value);
+ }
+
+ private async Task UpdateOneToManyAsync(IIdentifiable parent, RelationshipAttribute relationship, IEnumerable relationshipIds)
+ {
+ var value = new List();
+ if (relationshipIds.Any())
+ { // [1, 2, 3]
+ var target = Expression.Constant(TypeHelper.ConvertListType(relationshipIds, TypeHelper.GetIdentifierType(relationship.RightType)));
+ // (Person p) => ...
+ ParameterExpression parameter = Expression.Parameter(typeof(TRelatedResource));
+ // (Person p) => p.Id
+ Expression idMember = Expression.Property(parameter, nameof(Identifiable.Id));
+ // [1,2,3].Contains(p.Id)
+ var callContains = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { idMember.Type }, target, idMember);
+ var containsLamdda = Expression.Lambda>(callContains, parameter);
+ value = await _context.Set().Where(containsLamdda).ToListAsync();
+ }
+ relationship.SetValue(parent, value);
+ }
+
+ private async Task UpdateManyToManyAsync(IIdentifiable parent, HasManyThroughAttribute relationship, IEnumerable relationshipIds)
+ {
+ // we need to create a transaction for the HasManyThrough case so we can get and remove any existing
+ // join entities and only commit if all operations are successful
+ var transaction = await _context.GetCurrentOrCreateTransactionAsync();
+ // ArticleTag
+ ParameterExpression parameter = Expression.Parameter(relationship.ThroughType);
+ // ArticleTag.ArticleId
+ Expression idMember = Expression.Property(parameter, relationship.LeftIdProperty);
+ // article.Id
+ var parentId = TypeHelper.ConvertType(parent.StringId, relationship.LeftIdProperty.PropertyType);
+ Expression target = Expression.Constant(parentId);
+ // ArticleTag.ArticleId.Equals(article.Id)
+ Expression callEquals = Expression.Call(idMember, "Equals", null, target);
+ var lambda = Expression.Lambda>(callEquals, parameter);
+ // TODO: we shouldn't need to do this instead we should try updating the existing?
+ // the challenge here is if a composite key is used, then we will fail to
+ // create due to a unique key violation
+ var oldLinks = _context
+ .Set()
+ .Where(lambda.Compile())
+ .ToList();
+
+ _context.RemoveRange(oldLinks);
+
+ var newLinks = relationshipIds.Select(x =>
+ {
+ var link = Activator.CreateInstance(relationship.ThroughType);
+ relationship.LeftIdProperty.SetValue(link, TypeHelper.ConvertType(parentId, relationship.LeftIdProperty.PropertyType));
+ relationship.RightIdProperty.SetValue(link, TypeHelper.ConvertType(x, relationship.RightIdProperty.PropertyType));
+ return link;
+ });
+
+ _context.AddRange(newLinks);
+ await _context.SaveChangesAsync();
+ transaction.Commit();
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
index 00ecf71759..b747656b40 100644
--- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
+++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
@@ -1,10 +1,10 @@
4.0.0
- $(NetStandardVersion)
+ $(NetCoreAppVersion)
JsonApiDotNetCore
JsonApiDotNetCore
- 7.2
+ 8.0
@@ -20,14 +20,12 @@
+
-
-
-
+
-
diff --git a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs
index 8b8027120a..dcff276470 100644
--- a/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs
+++ b/src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs
@@ -138,7 +138,9 @@ internal static bool ContainsMediaTypeParameters(string mediaType)
// if the content type is not application/vnd.api+json then continue on
if (incomingMediaTypeSpan.Length < Constants.ContentType.Length)
+ {
return false;
+ }
var incomingContentType = incomingMediaTypeSpan.Slice(0, Constants.ContentType.Length);
if (incomingContentType.SequenceEqual(Constants.ContentType.AsSpan()) == false)
@@ -163,7 +165,9 @@ private void FlushResponse(HttpContext context, int statusCode)
///
private ResourceContext GetCurrentEntity()
{
- var controllerName = (string)_httpContext.GetRouteData().Values["controller"];
+ var controllerName = (string)_httpContext.GetRouteValue("controller");
+ if (controllerName == null)
+ return null;
var resourceType = _controllerResourceMapping.GetAssociatedResource(controllerName);
var requestResource = _resourceGraph.GetResourceContext(resourceType);
if (requestResource == null)
diff --git a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..7c21e6f218
--- /dev/null
+++ b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("IntegrationTests")]
diff --git a/src/JsonApiDotNetCore/Properties/launchSettings.json b/src/JsonApiDotNetCore/Properties/launchSettings.json
new file mode 100644
index 0000000000..d0f3094262
--- /dev/null
+++ b/src/JsonApiDotNetCore/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:63521/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "JsonApiDotNetCore": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:63522/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/JsonApiDotNetCore/Services/DefaultResourceService.cs b/src/JsonApiDotNetCore/Services/DefaultResourceService.cs
index 116fa009f8..ea6f4c7a87 100644
--- a/src/JsonApiDotNetCore/Services/DefaultResourceService.cs
+++ b/src/JsonApiDotNetCore/Services/DefaultResourceService.cs
@@ -177,15 +177,19 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa
if (entity == null)
throw new JsonApiException(404, $"Entity with id {id} could not be found.");
- List relatedEntities;
+ entity = IsNull(_hookExecutor) ? entity : _hookExecutor.BeforeUpdate(AsList(entity), ResourcePipeline.PatchRelationship).SingleOrDefault();
- if (relationship is HasOneAttribute)
- relatedEntities = new List { (IIdentifiable)related };
- else relatedEntities = (List)related;
- var relationshipIds = relatedEntities.Select(r => r?.StringId);
+ string[] relationshipIds = null;
+ if (related != null)
+ {
+ if (relationship is HasOneAttribute)
+ relationshipIds = new string[] { ((IIdentifiable)related).StringId };
+ else
+ relationshipIds = ((IEnumerable)related).Select(e => e.StringId).ToArray();
+ }
+
+ await _repository.UpdateRelationshipsAsync(entity, relationship, relationshipIds ?? new string[0] );
- entity = IsNull(_hookExecutor) ? entity : _hookExecutor.BeforeUpdate(AsList(entity), ResourcePipeline.PatchRelationship).SingleOrDefault();
- await _repository.UpdateRelationshipsAsync(entity, relationship, relationshipIds);
if (!IsNull(_hookExecutor, entity)) _hookExecutor.AfterUpdate(AsList(entity), ResourcePipeline.PatchRelationship);
}
diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs
index f7f643428f..cf87a3f537 100644
--- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs
+++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs
@@ -1,105 +1,105 @@
-using System.Collections.Generic;
-using GettingStarted.Models;
-using GettingStarted.ResourceDefinitionExample;
-using JsonApiDotNetCore.Builders;
+using System.Collections.Generic;
+using GettingStarted.Models;
+using GettingStarted.ResourceDefinitionExample;
+using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Configuration;
-using JsonApiDotNetCore.Data;
-using JsonApiDotNetCore.Graph;
+using JsonApiDotNetCore.Data;
+using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Hooks;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Internal.Generics;
using JsonApiDotNetCore.Managers.Contracts;
-using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Serialization.Server.Builders;
-using JsonApiDotNetCore.Services;
+using JsonApiDotNetCore.Services;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Moq;
-using Xunit;
-
-namespace DiscoveryTests
-{
- public class ServiceDiscoveryFacadeTests
- {
- private readonly IServiceCollection _services = new ServiceCollection();
+using Moq;
+using Xunit;
+
+namespace DiscoveryTests
+{
+ public class ServiceDiscoveryFacadeTests
+ {
+ private readonly IServiceCollection _services = new ServiceCollection();
private readonly ResourceGraphBuilder _resourceGraphBuilder = new ResourceGraphBuilder();
public ServiceDiscoveryFacadeTests()
{
- var contextMock = new Mock();
- var dbResolverMock = new Mock();
- dbResolverMock.Setup(m => m.GetContext()).Returns(new Mock().Object);
+ var contextMock = new Mock();
+ var dbResolverMock = new Mock();
+ dbResolverMock.Setup(m => m.GetContext()).Returns(new Mock().Object);
TestModelRepository._dbContextResolver = dbResolverMock.Object;
- _services.AddSingleton(new JsonApiOptions());
+ _services.AddSingleton(new JsonApiOptions());
_services.AddScoped((_) => new Mock().Object);
- _services.AddScoped((_) => new Mock().Object);
- _services.AddScoped((_) => new Mock().Object);
+ _services.AddScoped((_) => new Mock().Object);
+ _services.AddScoped((_) => new Mock().Object);
_services.AddScoped((_) => new Mock().Object);
_services.AddScoped((_) => new Mock().Object);
_services.AddScoped((_) => new Mock().Object);
}
- private ServiceDiscoveryFacade _facade => new ServiceDiscoveryFacade(_services, _resourceGraphBuilder);
-
- [Fact]
- public void AddAssembly_Adds_All_Resources_To_Graph()
- {
- // arrange, act
- _facade.AddAssembly(typeof(Person).Assembly);
-
- // assert
- var resourceGraph = _resourceGraphBuilder.Build();
- var personResource = resourceGraph.GetResourceContext(typeof(Person));
- var articleResource = resourceGraph.GetResourceContext(typeof(Article));
- var modelResource = resourceGraph.GetResourceContext(typeof(Model));
-
- Assert.NotNull(personResource);
- Assert.NotNull(articleResource);
- Assert.NotNull(modelResource);
- }
-
- [Fact]
- public void AddCurrentAssembly_Adds_Resources_To_Graph()
- {
- // arrange, act
- _facade.AddCurrentAssembly();
-
- // assert
- var resourceGraph = _resourceGraphBuilder.Build();
- var testModelResource = resourceGraph.GetResourceContext(typeof(TestModel));
- Assert.NotNull(testModelResource);
- }
-
- [Fact]
- public void AddCurrentAssembly_Adds_Services_To_Container()
+ private ServiceDiscoveryFacade _facade => new ServiceDiscoveryFacade(_services, _resourceGraphBuilder);
+
+ [Fact]
+ public void AddAssembly_Adds_All_Resources_To_Graph()
+ {
+ // Arrange, act
+ _facade.AddAssembly(typeof(Person).Assembly);
+
+ // Assert
+ var resourceGraph = _resourceGraphBuilder.Build();
+ var personResource = resourceGraph.GetResourceContext(typeof(Person));
+ var articleResource = resourceGraph.GetResourceContext(typeof(Article));
+ var modelResource = resourceGraph.GetResourceContext(typeof(Model));
+
+ Assert.NotNull(personResource);
+ Assert.NotNull(articleResource);
+ Assert.NotNull(modelResource);
+ }
+
+ [Fact]
+ public void AddCurrentAssembly_Adds_Resources_To_Graph()
+ {
+ // Arrange, act
+ _facade.AddCurrentAssembly();
+
+ // Assert
+ var resourceGraph = _resourceGraphBuilder.Build();
+ var testModelResource = resourceGraph.GetResourceContext(typeof(TestModel));
+ Assert.NotNull(testModelResource);
+ }
+
+ [Fact]
+ public void AddCurrentAssembly_Adds_Services_To_Container()
+ {
+ // Arrange, act
+ _facade.AddCurrentAssembly();
+
+ // Assert
+ var services = _services.BuildServiceProvider();
+ var service = services.GetService>();
+ Assert.IsType(service);
+ }
+
+ [Fact]
+ public void AddCurrentAssembly_Adds_Repositories_To_Container()
+ {
+ // Arrange, act
+ _facade.AddCurrentAssembly();
+
+ // Assert
+ var services = _services.BuildServiceProvider();
+ Assert.IsType(services.GetService>());
+ }
+
+ public class TestModel : Identifiable { }
+
+ public class TestModelService : DefaultResourceService
{
- // arrange, act
- _facade.AddCurrentAssembly();
-
- // assert
- var services = _services.BuildServiceProvider();
- var service = services.GetService>();
- Assert.IsType(service);
- }
-
- [Fact]
- public void AddCurrentAssembly_Adds_Repositories_To_Container()
- {
- // arrange, act
- _facade.AddCurrentAssembly();
-
- // assert
- var services = _services.BuildServiceProvider();
- Assert.IsType(services.GetService>());
- }
-
- public class TestModel : Identifiable { }
-
- public class TestModelService : DefaultResourceService
- {
private static IResourceRepository _repo = new Mock>().Object;
public TestModelService(IEnumerable queryParameters,
@@ -109,16 +109,16 @@ public TestModelService(IEnumerable queryParameters,
IResourceHookExecutor hookExecutor = null,
ILoggerFactory loggerFactory = null)
: base(queryParameters, options, repository, provider, hookExecutor, loggerFactory) { }
- }
-
- public class TestModelRepository : DefaultResourceRepository
- {
+ }
+
+ public class TestModelRepository : DefaultResourceRepository
+ {
internal static IDbContextResolver _dbContextResolver;
public TestModelRepository(ITargetedFields targetedFields,
IResourceGraph resourceGraph,
IGenericServiceFactory genericServiceFactory)
: base(targetedFields, _dbContextResolver, resourceGraph, genericServiceFactory) { }
- }
- }
-}
+ }
+ }
+}
diff --git a/test/IntegrationTests/Data/EntityRepositoryTests.cs b/test/IntegrationTests/Data/EntityRepositoryTests.cs
new file mode 100644
index 0000000000..e0699ceb2b
--- /dev/null
+++ b/test/IntegrationTests/Data/EntityRepositoryTests.cs
@@ -0,0 +1,195 @@
+using JsonApiDotNetCore.Builders;
+using JsonApiDotNetCore.Data;
+using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Serialization;
+using JsonApiDotNetCoreExample.Data;
+using JsonApiDotNetCoreExample.Models;
+using Microsoft.EntityFrameworkCore;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+
+namespace JADNC.IntegrationTests.Data
+{
+ public class EntityRepositoryTests
+ {
+
+
+ public EntityRepositoryTests()
+ {
+ }
+
+ [Fact]
+ public async Task UpdateAsync_AttributesUpdated_ShouldHaveSpecificallyThoseAttributesUpdated()
+ {
+ // Arrange
+ var itemId = 213;
+ var seed = Guid.NewGuid();
+ using (var arrangeContext = GetContext(seed))
+ {
+ var (repository, targetedFields) = Setup(arrangeContext);
+ var todoItemUpdates = new TodoItem
+ {
+ Id = itemId,
+ Description = Guid.NewGuid().ToString()
+ };
+ arrangeContext.Add(todoItemUpdates);
+ arrangeContext.SaveChanges();
+
+ var descAttr = new AttrAttribute("description", "Description")
+ {
+ PropertyInfo = typeof(TodoItem).GetProperty(nameof(TodoItem.Description))
+ };
+ targetedFields.Setup(m => m.Attributes).Returns(new List { descAttr });
+ targetedFields.Setup(m => m.Relationships).Returns(new List());
+
+ // Act
+ var updatedItem = await repository.UpdateAsync(todoItemUpdates);
+ }
+
+ // Assert - in different context
+ using var assertContext = GetContext(seed);
+ {
+ var (repository, targetedFields) = Setup(assertContext);
+
+ var fetchedTodo = repository.Get(itemId).First();
+ Assert.NotNull(fetchedTodo);
+ Assert.Equal(fetchedTodo.Ordinal, fetchedTodo.Ordinal);
+ Assert.Equal(fetchedTodo.Description, fetchedTodo.Description);
+
+ }
+ }
+
+ [Theory]
+ [InlineData(3, 2, new[] { 4, 5, 6 })]
+ [InlineData(8, 2, new[] { 9 })]
+ [InlineData(20, 1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })]
+ public async Task Paging_PageNumberIsPositive_ReturnCorrectIdsAtTheFront(int pageSize, int pageNumber, int[] expectedResult)
+ {
+ // Arrange
+ using var context = GetContext();
+ var (repository, targetedFields) = Setup(context);
+ context.AddRange(TodoItems(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await repository.PageAsync(context.Set(), pageSize, pageNumber);
+
+ // Assert
+ Assert.Equal(TodoItems(expectedResult), result, new IdComparer());
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(-1)]
+ [InlineData(-10)]
+ public async Task Paging_PageSizeNonPositive_DoNothing(int pageSize)
+ {
+ // Arrange
+ using var context = GetContext();
+ var (repository, targetedFields) = Setup(context);
+ var items = TodoItems(2, 3, 1);
+ context.AddRange(items);
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await repository.PageAsync(context.Set(), pageSize, 3);
+
+ // Assert
+ Assert.Equal(items.ToList(), result.ToList(), new IdComparer());
+ }
+
+ [Fact]
+ public async Task Paging_PageNumberDoesNotExist_ReturnEmptyAQueryable()
+ {
+ // Arrange
+ var items = TodoItems(2, 3, 1);
+ using var context = GetContext();
+ var (repository, targetedFields) = Setup(context);
+ context.AddRange(items);
+
+ // Act
+ var result = await repository.PageAsync(context.Set(), 2, 3);
+
+ // Assert
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public async Task Paging_PageNumberIsZero_PretendsItsOne()
+ {
+ // Arrange
+ using var context = GetContext();
+ var (repository, targetedFields) = Setup(context);
+ context.AddRange(TodoItems(2, 3, 4, 5, 6, 7, 8, 9));
+ await context.SaveChangesAsync();
+
+ // Act
+ var result = await repository.PageAsync(entities: context.Set(), pageSize: 1, pageNumber: 0);
+
+ // Assert
+ Assert.Equal(TodoItems(2), result, new IdComparer());
+ }
+
+ [Theory]
+ [InlineData(6, -1, new[] { 4, 5, 6, 7, 8, 9 })]
+ [InlineData(6, -2, new[] { 1, 2, 3 })]
+ [InlineData(20, -1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })]
+ public async Task Paging_PageNumberIsNegative_GiveBackReverseAmountOfIds(int pageSize, int pageNumber, int[] expectedIds)
+ {
+ // Arrange
+ using var context = GetContext();
+ var repository = Setup(context).Repository;
+ context.AddRange(TodoItems(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ context.SaveChanges();
+
+ // Act
+ var result = await repository.PageAsync(context.Set(), pageSize, pageNumber);
+
+ // Assert
+ Assert.Equal(TodoItems(expectedIds), result, new IdComparer());
+ }
+
+
+ private (DefaultResourceRepository Repository, Mock TargetedFields) Setup(AppDbContext context)
+ {
+ var contextResolverMock = new Mock();
+ contextResolverMock.Setup(m => m.GetContext()).Returns(context);
+ var resourceGraph = new ResourceGraphBuilder().AddResource().Build();
+ var targetedFields = new Mock();
+ var repository = new DefaultResourceRepository(targetedFields.Object, contextResolverMock.Object, resourceGraph, null);
+ return (repository, targetedFields);
+ }
+
+ private AppDbContext GetContext(Guid? seed = null)
+ {
+ Guid actualSeed = seed == null ? Guid.NewGuid() : seed.GetValueOrDefault();
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(databaseName: $"IntegrationDatabaseRepository{actualSeed}")
+ .Options;
+ var context = new AppDbContext(options);
+
+ context.TodoItems.RemoveRange(context.TodoItems.ToList());
+ return context;
+ }
+
+ private static TodoItem[] TodoItems(params int[] ids)
+ {
+ return ids.Select(id => new TodoItem { Id = id }).ToArray();
+ }
+
+ private class IdComparer : IEqualityComparer
+ where T : IIdentifiable
+ {
+ public bool Equals(T x, T y) => x?.StringId == y?.StringId;
+
+ public int GetHashCode(T obj) => obj?.StringId?.GetHashCode() ?? 0;
+ }
+ }
+}
diff --git a/test/IntegrationTests/IntegrationTests.csproj b/test/IntegrationTests/IntegrationTests.csproj
new file mode 100644
index 0000000000..23141edea5
--- /dev/null
+++ b/test/IntegrationTests/IntegrationTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netcoreapp3.0
+
+ false
+
+ JADNC.IntegrationTests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs
index 8399ff36ed..a9b827ebc5 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs
@@ -17,11 +17,11 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance
[Collection("WebHostCollection")]
public class CamelCasedModelsControllerTests
{
- private TestFixture _fixture;
+ private TestFixture _fixture;
private AppDbContext _context;
private Faker _faker;
- public CamelCasedModelsControllerTests(TestFixture fixture)
+ public CamelCasedModelsControllerTests(TestFixture fixture)
{
_fixture = fixture;
_context = fixture.GetService();
@@ -66,11 +66,13 @@ public async Task Can_Get_CamelCasedModels_ById()
var httpMethod = new HttpMethod("GET");
var route = $"api/v1/camelCasedModels/{model.Id}";
+ var request = new HttpRequestMessage(httpMethod, route);
+
+ // unnecessary, will fix in 4.1
var builder = new WebHostBuilder()
- .UseStartup();
+ .UseStartup();
var server = new TestServer(builder);
var client = server.CreateClient();
- var request = new HttpRequestMessage(httpMethod, route);
// Act
var response = await client.SendAsync(request);
@@ -123,7 +125,7 @@ public async Task Can_Post_CamelCasedModels()
}
[Fact]
- public async Task Can_Patch_CamelCasedModels()
+ public async Task RoutingPatch_RouteIsCamelcased_ResponseOKAndCompoundAttrIsAvailable()
{
// Arrange
var model = _faker.Generate();
@@ -143,10 +145,11 @@ public async Task Can_Patch_CamelCasedModels()
}
}
};
- var httpMethod = new HttpMethod("PATCH");
+ var httpMethod = HttpMethod.Patch;
var route = $"api/v1/camelCasedModels/{model.Id}";
var builder = new WebHostBuilder().UseStartup();
- var server = new TestServer(builder);
+
+ using var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
request.Content = new StringContent(JsonConvert.SerializeObject(content));
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs
index 04b530eab8..4a23da42ac 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs
@@ -17,11 +17,11 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility
[Collection("WebHostCollection")]
public class CustomControllerTests
{
- private TestFixture _fixture;
+ private TestFixture _fixture;
private Faker _todoItemFaker;
private Faker _personFaker;
- public CustomControllerTests(TestFixture fixture)
+ public CustomControllerTests(TestFixture fixture)
{
_fixture = fixture;
_todoItemFaker = new Faker()
@@ -35,7 +35,7 @@ public CustomControllerTests(TestFixture fixture)
[Fact]
public async Task NonJsonApiControllers_DoNotUse_Dasherized_Routes()
{
- // arrange
+ // Arrange
var builder = new WebHostBuilder()
.UseStartup();
var httpMethod = new HttpMethod("GET");
@@ -45,10 +45,10 @@ public async Task NonJsonApiControllers_DoNotUse_Dasherized_Routes()
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
- // act
+ // Act
var response = await client.SendAsync(request);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
@@ -65,10 +65,10 @@ public async Task CustomRouteControllers_Uses_Dasherized_Collection_Route()
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
- // act
+ // Act
var response = await client.SendAsync(request);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
@@ -92,10 +92,10 @@ public async Task CustomRouteControllers_Uses_Dasherized_Item_Route()
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
- // act
+ // Act
var response = await client.SendAsync(request);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs
index 0f2026e07c..9081dac41e 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs
@@ -3,6 +3,7 @@
using System.Threading.Tasks;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Models;
+using JsonApiDotNetCoreExample;
using JsonApiDotNetCoreExample.Data;
using JsonApiDotNetCoreExample.Models;
using Newtonsoft.Json;
@@ -13,11 +14,11 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility
[Collection("WebHostCollection")]
public class NullValuedAttributeHandlingTests : IAsyncLifetime
{
- private readonly TestFixture _fixture;
+ private readonly TestFixture _fixture;
private readonly AppDbContext _dbContext;
private readonly TodoItem _todoItem;
- public NullValuedAttributeHandlingTests(TestFixture fixture)
+ public NullValuedAttributeHandlingTests(TestFixture fixture)
{
_fixture = fixture;
_dbContext = fixture.GetService();
@@ -86,12 +87,12 @@ public async Task CheckNullBehaviorCombination(bool? omitNullValuedAttributes, b
var route = $"/api/v1/todo-items/{_todoItem.Id}?include=owner{queryString}";
var request = new HttpRequestMessage(httpMethod, route);
- // act
+ // Act
var response = await _fixture.Client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var deserializeBody = JsonConvert.DeserializeObject(body);
- // assert: does response contain a null valued attribute?
+ // Assert: does response contain a null valued attribute?
Assert.Equal(omitsNulls, !deserializeBody.SingleData.Attributes.ContainsKey("description"));
Assert.Equal(omitsNulls, !deserializeBody.Included[0].Attributes.ContainsKey("last-name"));
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs
index 31fe906e4a..5f31c447b6 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs
@@ -5,20 +5,18 @@
using Microsoft.AspNetCore.TestHost;
using Xunit;
using JsonApiDotNetCoreExample.Models;
-using Newtonsoft.Json;
using JsonApiDotNetCore.Models;
using System.Collections;
-using JsonApiDotNetCoreExampleTests.Startups;
-using JsonApiDotNetCoreExample.Resources;
+using JsonApiDotNetCoreExample;
namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility
{
[Collection("WebHostCollection")]
public class RequestMetaTests
{
- private TestFixture _fixture;
+ private TestFixture _fixture;
- public RequestMetaTests(TestFixture fixture)
+ public RequestMetaTests(TestFixture fixture)
{
_fixture = fixture;
}
@@ -26,7 +24,7 @@ public RequestMetaTests(TestFixture fixture)
[Fact]
public async Task Injecting_IRequestMeta_Adds_Meta_Data()
{
- // arrange
+ // Arrange
var builder = new WebHostBuilder()
.UseStartup();
@@ -38,12 +36,12 @@ public async Task Injecting_IRequestMeta_Adds_Meta_Data()
var request = new HttpRequestMessage(httpMethod, route);
var expectedMeta = (_fixture.GetService>() as IHasMeta).GetMeta();
- // act
+ // Act
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var meta = _fixture.GetDeserializer().DeserializeList(body).Meta;
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(meta);
Assert.NotNull(expectedMeta);
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs
index 88906ba7ab..0d3de444ee 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs
@@ -28,42 +28,42 @@ public async Task Allows_GET_Requests()
[Fact]
public async Task Rejects_POST_Requests()
{
- // arrange
+ // Arrange
const string route = "readonly";
const string method = "POST";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.MethodNotAllowed, statusCode);
}
[Fact]
public async Task Rejects_PATCH_Requests()
{
- // arrange
+ // Arrange
const string route = "readonly";
const string method = "PATCH";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.MethodNotAllowed, statusCode);
}
[Fact]
public async Task Rejects_DELETE_Requests()
{
- // arrange
+ // Arrange
const string route = "readonly";
const string method = "DELETE";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.MethodNotAllowed, statusCode);
}
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpDeleteTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpDeleteTests.cs
index 32e7eaf109..e7aa542d9a 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpDeleteTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpDeleteTests.cs
@@ -14,56 +14,56 @@ public class nohttpdeleteTests
[Fact]
public async Task Allows_GET_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttpdelete";
const string method = "GET";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Allows_POST_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttpdelete";
const string method = "POST";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Allows_PATCH_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttpdelete";
const string method = "PATCH";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Rejects_DELETE_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttpdelete";
const string method = "DELETE";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.MethodNotAllowed, statusCode);
}
@@ -79,4 +79,4 @@ private async Task MakeRequestAsync(string route, string method)
return response.StatusCode;
}
}
-}
\ No newline at end of file
+}
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPatchTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPatchTests.cs
index 5b8a33f16a..573b8dccf7 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPatchTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPatchTests.cs
@@ -14,56 +14,56 @@ public class nohttppatchTests
[Fact]
public async Task Allows_GET_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppatch";
const string method = "GET";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Allows_POST_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppatch";
const string method = "POST";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Rejects_PATCH_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppatch";
const string method = "PATCH";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.MethodNotAllowed, statusCode);
}
[Fact]
public async Task Allows_DELETE_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppatch";
const string method = "DELETE";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
@@ -79,4 +79,4 @@ private async Task MakeRequestAsync(string route, string method)
return response.StatusCode;
}
}
-}
\ No newline at end of file
+}
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPostTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPostTests.cs
index f68a65a037..b1dda90c29 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPostTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/NoHttpPostTests.cs
@@ -14,56 +14,56 @@ public class NoHttpPostTests
[Fact]
public async Task Allows_GET_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppost";
const string method = "GET";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Rejects_POST_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppost";
const string method = "POST";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.MethodNotAllowed, statusCode);
}
[Fact]
public async Task Allows_PATCH_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppost";
const string method = "PATCH";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
[Fact]
public async Task Allows_DELETE_Requests()
{
- // arrange
+ // Arrange
const string route = "nohttppost";
const string method = "DELETE";
- // act
+ // Act
var statusCode = await MakeRequestAsync(route, method);
- // assert
+ // Assert
Assert.Equal(HttpStatusCode.OK, statusCode);
}
@@ -79,4 +79,4 @@ private async Task MakeRequestAsync(string route, string method)
return response.StatusCode;
}
}
-}
\ No newline at end of file
+}
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs
index 4fe635952d..1df5593fb0 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs
@@ -6,8 +6,11 @@
using System.Threading.Tasks;
using Bogus;
using JsonApiDotNetCore.Models;
+using JsonApiDotNetCoreExample;
using JsonApiDotNetCoreExample.Data;
using JsonApiDotNetCoreExample.Models;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Xunit;
@@ -23,8 +26,8 @@ public class ManyToManyTests
private static readonly Faker _tagFaker = new Faker().RuleFor(a => a.Name, f => f.Random.AlphaNumeric(10));
- private TestFixture _fixture;
- public ManyToManyTests(TestFixture fixture)
+ private TestFixture _fixture;
+ public ManyToManyTests(TestFixture fixture)
{
_fixture = fixture;
}
@@ -32,7 +35,7 @@ public ManyToManyTests(TestFixture fixture)
[Fact]
public async Task Can_Fetch_Many_To_Many_Through_All()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var article = _articleFaker.Generate();
var tag = _tagFaker.Generate();
@@ -47,13 +50,18 @@ public async Task Can_Fetch_Many_To_Many_Through_All()
};
context.ArticleTags.Add(articleTag);
await context.SaveChangesAsync();
-
var route = $"/api/v1/articles?include=tags";
- // act
- var response = await _fixture.Client.GetAsync(route);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync(route);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -75,7 +83,7 @@ public async Task Can_Fetch_Many_To_Many_Through_All()
[Fact]
public async Task Can_Fetch_Many_To_Many_Through_GetById()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var article = _articleFaker.Generate();
var tag = _tagFaker.Generate();
@@ -89,10 +97,16 @@ public async Task Can_Fetch_Many_To_Many_Through_GetById()
var route = $"/api/v1/articles/{article.Id}?include=tags";
- // act
- var response = await _fixture.Client.GetAsync(route);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
- // assert
+ // Act
+ var response = await client.GetAsync(route);
+
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -111,7 +125,7 @@ public async Task Can_Fetch_Many_To_Many_Through_GetById()
[Fact]
public async Task Can_Fetch_Many_To_Many_Through_GetById_Relationship_Link()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var article = _articleFaker.Generate();
var tag = _tagFaker.Generate();
@@ -125,10 +139,16 @@ public async Task Can_Fetch_Many_To_Many_Through_GetById_Relationship_Link()
var route = $"/api/v1/articles/{article.Id}/tags";
- // act
- var response = await _fixture.Client.GetAsync(route);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync(route);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -144,7 +164,7 @@ public async Task Can_Fetch_Many_To_Many_Through_GetById_Relationship_Link()
[Fact]
public async Task Can_Fetch_Many_To_Many_Through_Relationship_Link()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var article = _articleFaker.Generate();
var tag = _tagFaker.Generate();
@@ -157,11 +177,17 @@ public async Task Can_Fetch_Many_To_Many_Through_Relationship_Link()
await context.SaveChangesAsync();
var route = $"/api/v1/articles/{article.Id}/relationships/tags";
+
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
- // act
- var response = await _fixture.Client.GetAsync(route);
+ // Act
+ var response = await client.GetAsync(route);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -176,7 +202,7 @@ public async Task Can_Fetch_Many_To_Many_Through_Relationship_Link()
[Fact]
public async Task Can_Fetch_Many_To_Many_Without_Include()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var article = _articleFaker.Generate();
var tag = _tagFaker.Generate();
@@ -187,13 +213,18 @@ public async Task Can_Fetch_Many_To_Many_Without_Include()
};
context.ArticleTags.Add(articleTag);
await context.SaveChangesAsync();
-
var route = $"/api/v1/articles/{article.Id}";
- // act
- var response = await _fixture.Client.GetAsync(route);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync(route);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -204,7 +235,7 @@ public async Task Can_Fetch_Many_To_Many_Without_Include()
[Fact]
public async Task Can_Create_Many_To_Many()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var tag = _tagFaker.Generate();
var author = new Author();
@@ -242,14 +273,19 @@ public async Task Can_Create_Many_To_Many()
}
}
};
-
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
- // act
- var response = await _fixture.Client.SendAsync(request);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.SendAsync(request);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.Created == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -267,7 +303,7 @@ public async Task Can_Create_Many_To_Many()
[Fact]
public async Task Can_Update_Many_To_Many()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var tag = _tagFaker.Generate();
var article = _articleFaker.Generate();
@@ -299,10 +335,16 @@ public async Task Can_Update_Many_To_Many()
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
- // act
- var response = await _fixture.Client.SendAsync(request);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder()
+ .UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.SendAsync(request);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -321,7 +363,7 @@ public async Task Can_Update_Many_To_Many()
[Fact]
public async Task Can_Update_Many_To_Many_With_Complete_Replacement()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var firstTag = _tagFaker.Generate();
var article = _articleFaker.Generate();
@@ -359,10 +401,15 @@ public async Task Can_Update_Many_To_Many_With_Complete_Replacement()
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
- // act
- var response = await _fixture.Client.SendAsync(request);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder().UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
- // assert
+ // Act
+ var response = await client.SendAsync(request);
+
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -380,7 +427,7 @@ public async Task Can_Update_Many_To_Many_With_Complete_Replacement()
[Fact]
public async Task Can_Update_Many_To_Many_With_Complete_Replacement_With_Overlap()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var firstTag = _tagFaker.Generate();
var article = _articleFaker.Generate();
@@ -422,10 +469,15 @@ public async Task Can_Update_Many_To_Many_With_Complete_Replacement_With_Overlap
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
- // act
- var response = await _fixture.Client.SendAsync(request);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder().UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.SendAsync(request);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
@@ -443,7 +495,7 @@ public async Task Can_Update_Many_To_Many_With_Complete_Replacement_With_Overlap
[Fact]
public async Task Can_Update_Many_To_Many_Through_Relationship_Link()
{
- // arrange
+ // Arrange
var context = _fixture.GetService();
var tag = _tagFaker.Generate();
var article = _articleFaker.Generate();
@@ -466,10 +518,15 @@ public async Task Can_Update_Many_To_Many_Through_Relationship_Link()
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
- // act
- var response = await _fixture.Client.SendAsync(request);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder().UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.SendAsync(request);
- // assert
+ // Assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs
index 0a7a56ee9e..6c3c23b125 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs
@@ -3,8 +3,11 @@
using System.Net.Http;
using System.Threading.Tasks;
using Bogus;
+using JsonApiDotNetCoreExample;
using JsonApiDotNetCoreExample.Data;
using JsonApiDotNetCoreExample.Models;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
using Xunit;
namespace JsonApiDotNetCoreExampleTests.Acceptance
@@ -12,11 +15,11 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance
[Collection("WebHostCollection")]
public class QueryFiltersTests
{
- private TestFixture _fixture;
+ private TestFixture _fixture;
private AppDbContext _context;
private Faker _userFaker;
- public QueryFiltersTests(TestFixture fixture)
+ public QueryFiltersTests(TestFixture fixture)
{
_fixture = fixture;
_context = fixture.GetService();
@@ -38,8 +41,13 @@ public async Task FiltersWithCustomQueryFiltersEquals()
var route = $"/api/v1/users?filter[first-character]=eq:{firstUsernameCharacter}";
var request = new HttpRequestMessage(httpMethod, route);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder().UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
// Act
- var response = await _fixture.Client.SendAsync(request);
+ var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -66,8 +74,13 @@ public async Task FiltersWithCustomQueryFiltersLessThan()
var route = $"/api/v1/users?filter[first-character]=lt:{median}";
var request = new HttpRequestMessage(httpMethod, route);
+ // @TODO - Use fixture
+ var builder = new WebHostBuilder().UseStartup();
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
// Act
- var response = await _fixture.Client.SendAsync(request);
+ var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs
index ad4994cc7a..0ae0806574 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Bogus;
using JsonApiDotNetCore.Models;
+using JsonApiDotNetCoreExample;
using JsonApiDotNetCoreExample.Data;
using JsonApiDotNetCoreExample.Models;
using Microsoft.EntityFrameworkCore;
@@ -18,7 +19,7 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance
[Collection("WebHostCollection")]
public class ResourceDefinitionTests
{
- private TestFixture _fixture;
+ private TestFixture _fixture;
private AppDbContext _context;
private Faker _userFaker;
private Faker _todoItemFaker;
@@ -28,7 +29,7 @@ public class ResourceDefinitionTests
.RuleFor(a => a.Author, f => new Author());
private static readonly Faker _tagFaker = new Faker().RuleFor(a => a.Name, f => f.Random.AlphaNumeric(10));
- public ResourceDefinitionTests(TestFixture fixture)
+ public ResourceDefinitionTests(TestFixture fixture)
{
_fixture = fixture;
_context = fixture.GetService();
@@ -73,6 +74,7 @@ public async Task Can_Create_User_With_Password()
var user = _userFaker.Generate();
var serializer = _fixture.GetSerializer(p => new { p.Password, p.Username });
+
var httpMethod = new HttpMethod("POST");
var route = $"/api/v1/users";
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs
index 3ae12cfdcb..25fd53c6f3 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Bogus;
using JsonApiDotNetCore.Models;
+using JsonApiDotNetCoreExample;
using JsonApiDotNetCoreExample.Data;
using JsonApiDotNetCoreExample.Models;
using Newtonsoft.Json;
@@ -17,11 +18,11 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec
[Collection("WebHostCollection")]
public class AttributeFilterTests
{
- private TestFixture _fixture;
+ private TestFixture _fixture;
private Faker _todoItemFaker;
private readonly Faker _personFaker;
- public AttributeFilterTests(TestFixture fixture)
+ public AttributeFilterTests(TestFixture fixture)
{
_fixture = fixture;
_todoItemFaker = new Faker()
@@ -37,7 +38,7 @@ public AttributeFilterTests(TestFixture fixture)
[Fact]
public async Task Can_Filter_On_Guid_Properties()
{
- // arrange
+ // Arrange
var context = _fixture.GetService