diff --git a/.travis.yml b/.travis.yml index 7ac38bba9f..9c5e937589 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: csharp dist: trusty sudo: required -services: +services: - postgresql before_script: - psql -c 'create database JsonApiDotNetCoreExample;' -U postgres mono: none -dotnet: 2.1.105 # https://www.microsoft.com/net/download/linux +dotnet: 2.1.300 # https://www.microsoft.com/net/download/linux branches: only: - master diff --git a/Directory.Build.props b/Directory.Build.props index 346835dd6c..0d034e0c5d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,28 +4,27 @@ netcoreapp2.0 netstandard2.0 - 2.0.1 + 2.1.0 - 2.0.0 - 2.0.0 - 2.0.0 + 2.1.0 + 2.1.0 + 2.1.0 - 2.0.1 - 2.0.1 + 2.1.0 + 2.1.0 - 3.2.6 - 2.0.0 + 4.0.0 + 2.1.0 - 4.4.0 + 4.5.0 - 15.3.0-preview-20170427-09 - 1.1.2 - 2.3.0-beta3-build3705 - 15.0.3 - 4.7.99 + 15.7.2 + 2.3.1 + 22.1.2 + 4.8.3 diff --git a/README.md b/README.md index 2250ef4568..81ebba79bf 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,14 @@ These are some steps you can take to help you understand what this project is an - [The json:api specification](http://jsonapi.org/format/) - [Demo [Video]](https://youtu.be/KAMuo6K7VcE) - [Our documentation](https://json-api-dotnet.github.io) -- Check out the examples in the next section +- [Check out the example projects](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/src/Examples) + +## Related Projects + +- [Performance Reports](https://github.com/json-api-dotnet/PerformanceReports) +- [JsonApiDotNetCore.MongoDb](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb) +- [JsonApiDotNetCore.Marten](https://github.com/wayne-o/JsonApiDotNetCore.Marten) +- [Todo List App](https://github.com/json-api-dotnet/TodoListExample) ## Examples diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj index 1e8b227f3c..60a9a8bf42 100644 --- a/benchmarks/Benchmarks.csproj +++ b/benchmarks/Benchmarks.csproj @@ -5,11 +5,11 @@ Benchmarks - + - + diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs index 57fafc8fc6..2515b1ea7a 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/Restricted/ReadOnlyController.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCoreExample.Controllers.Restricted { [Route("[controller]")] [HttpReadOnly] - public class ReadOnlyController : Controller + public class ReadOnlyController : ControllerBase { [HttpGet] public IActionResult Get() => Ok(); @@ -22,7 +22,7 @@ public class ReadOnlyController : Controller [Route("[controller]")] [NoHttpPost] - public class NoHttpPostController : Controller + public class NoHttpPostController : ControllerBase { [HttpGet] public IActionResult Get() => Ok(); @@ -39,7 +39,7 @@ public class NoHttpPostController : Controller [Route("[controller]")] [NoHttpPatch] - public class NoHttpPatchController : Controller + public class NoHttpPatchController : ControllerBase { [HttpGet] public IActionResult Get() => Ok(); @@ -56,7 +56,7 @@ public class NoHttpPatchController : Controller [Route("[controller]")] [NoHttpDelete] - public class NoHttpDeleteController : Controller + public class NoHttpDeleteController : ControllerBase { [HttpGet] public IActionResult Get() => Ok(); diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs index 3443d34b74..a29295c426 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TestValuesController.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCoreExample.Controllers { [Route("[controller]")] - public class TestValuesController : Controller + public class TestValuesController : ControllerBase { [HttpGet] public IActionResult Get() diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs index de784c129a..f5f3e111d9 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs @@ -33,17 +33,12 @@ public CustomJsonApiController( } public class CustomJsonApiController - : Controller where T : class, IIdentifiable + : ControllerBase where T : class, IIdentifiable { private readonly ILogger _logger; private readonly IResourceService _resourceService; private readonly IJsonApiContext _jsonApiContext; - protected IActionResult UnprocessableEntity() - { - return new StatusCodeResult(422); - } - protected IActionResult Forbidden() { return new StatusCodeResult(403); diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj index 94e2a404a9..fd657e83bf 100755 --- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj +++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj @@ -26,8 +26,4 @@ - - - - diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs index c86425b00c..c9788bf82f 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.Designer.cs @@ -1,4 +1,4 @@ -// +// using JsonApiDotNetCoreExample.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; using System; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace JsonApiDotNetCoreExample.Migrations { diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs index ba19b62ef6..cc696f54bf 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs @@ -1,7 +1,8 @@ -using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using System; using System.Collections.Generic; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace JsonApiDotNetCoreExample.Migrations { diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs index c0794103fe..08c284393e 100755 --- a/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using JsonApiDotNetCoreExample.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; using System; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace JsonApiDotNetCoreExample.Migrations { diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 1388004a55..2c7574e1a2 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -28,20 +28,17 @@ public Startup(IHostingEnvironment env) public virtual IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); - loggerFactory.AddConsole(LogLevel.Trace); - services.AddSingleton(loggerFactory); + loggerFactory.AddConsole(LogLevel.Warning); - services.AddDbContext(options => - { - options.UseNpgsql(GetDbConnectionString()); - }, ServiceLifetime.Transient); - - services.AddJsonApi(opt => - { - opt.Namespace = "api/v1"; - opt.DefaultPageSize = 5; - opt.IncludeTotalRecordCount = true; - }); + services + .AddSingleton(loggerFactory) + .AddDbContext(options => + options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) + .AddJsonApi(options => { + options.Namespace = "api/v1"; + options.DefaultPageSize = 5; + options.IncludeTotalRecordCount = true; + }); var provider = services.BuildServiceProvider(); var appContext = provider.GetRequiredService(); @@ -60,11 +57,10 @@ public virtual void Configure( context.Database.EnsureCreated(); loggerFactory.AddConsole(Config.GetSection("Logging")); - loggerFactory.AddDebug(); app.UseJsonApi(); } public string GetDbConnectionString() => Config["Data:DefaultConnection"]; } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/appsettings.json b/src/Examples/JsonApiDotNetCoreExample/appsettings.json index 578225ef14..c468439079 100755 --- a/src/Examples/JsonApiDotNetCoreExample/appsettings.json +++ b/src/Examples/JsonApiDotNetCoreExample/appsettings.json @@ -5,9 +5,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Trace", - "System": "Trace", - "Microsoft": "Trace" + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" } } } diff --git a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj index eed5f1b09e..efdaa68e5b 100755 --- a/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj +++ b/src/Examples/NoEntityFrameworkExample/NoEntityFrameworkExample.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Examples/NoEntityFrameworkExample/Startup.cs b/src/Examples/NoEntityFrameworkExample/Startup.cs index dfba27ddd9..711b54cfa3 100755 --- a/src/Examples/NoEntityFrameworkExample/Startup.cs +++ b/src/Examples/NoEntityFrameworkExample/Startup.cs @@ -1,4 +1,4 @@ -using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; @@ -31,7 +31,7 @@ public Startup(IHostingEnvironment env) public virtual IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. - var mvcBuilder = services.AddMvc(); + var mvcBuilder = services.AddMvcCore(); services.AddJsonApi(options => { options.Namespace = "api/v1"; @@ -55,7 +55,6 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, AppDbContext context) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - loggerFactory.AddDebug(); context.Database.EnsureCreated(); diff --git a/src/Examples/OperationsExample/OperationsExample.csproj b/src/Examples/OperationsExample/OperationsExample.csproj index 69ad5653ce..efb3f2b3d4 100644 --- a/src/Examples/OperationsExample/OperationsExample.csproj +++ b/src/Examples/OperationsExample/OperationsExample.csproj @@ -8,7 +8,7 @@ - + @@ -27,8 +27,4 @@ - - - - diff --git a/src/Examples/OperationsExample/Startup.cs b/src/Examples/OperationsExample/Startup.cs index c11a3e9d26..a889ad85d6 100644 --- a/src/Examples/OperationsExample/Startup.cs +++ b/src/Examples/OperationsExample/Startup.cs @@ -27,7 +27,7 @@ public Startup(IHostingEnvironment env) public virtual IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); - loggerFactory.AddConsole(LogLevel.Trace); + loggerFactory.AddConsole(LogLevel.Warning); services.AddSingleton(loggerFactory); @@ -47,7 +47,6 @@ public virtual void Configure( context.Database.EnsureCreated(); loggerFactory.AddConsole(Config.GetSection("Logging")); - loggerFactory.AddDebug(); app.UseJsonApi(); } diff --git a/src/Examples/OperationsExample/appsettings.json b/src/Examples/OperationsExample/appsettings.json index 0d77cd40f2..73030b1743 100644 --- a/src/Examples/OperationsExample/appsettings.json +++ b/src/Examples/OperationsExample/appsettings.json @@ -5,9 +5,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Trace", - "System": "Trace", - "Microsoft": "Trace" + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" } } } diff --git a/src/Examples/ReportsExample/ReportsExample.csproj b/src/Examples/ReportsExample/ReportsExample.csproj index bd4b402071..24c01b9a8d 100644 --- a/src/Examples/ReportsExample/ReportsExample.csproj +++ b/src/Examples/ReportsExample/ReportsExample.csproj @@ -13,11 +13,11 @@ - + - + diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs index 72710681b9..fe476c0406 100644 --- a/src/Examples/ReportsExample/Startup.cs +++ b/src/Examples/ReportsExample/Startup.cs @@ -25,7 +25,7 @@ public Startup(IHostingEnvironment env) public virtual void ConfigureServices(IServiceCollection services) { - var mvcBuilder = services.AddMvc(); + var mvcBuilder = services.AddMvcCore(); services.AddJsonApi(opt => { opt.BuildContextGraph(builder => diff --git a/src/Examples/ReportsExample/appsettings.json b/src/Examples/ReportsExample/appsettings.json index 125f7a4ae9..5766595e6d 100644 --- a/src/Examples/ReportsExample/appsettings.json +++ b/src/Examples/ReportsExample/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Information" + "Default": "Warning" } } } diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs index d8effd4fe3..fe014bced5 100644 --- a/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs +++ b/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; - namespace JsonApiDotNetCore.Builders { public interface IDocumentBuilderOptionsProvider { - DocumentBuilderOptions GetDocumentBuilderOptions(); + DocumentBuilderOptions GetDocumentBuilderOptions(); } } diff --git a/src/JsonApiDotNetCore/Configuration/NullAttributeResponseBehavior.cs b/src/JsonApiDotNetCore/Configuration/NullAttributeResponseBehavior.cs index 1b10140f5e..125d38b5fc 100644 --- a/src/JsonApiDotNetCore/Configuration/NullAttributeResponseBehavior.cs +++ b/src/JsonApiDotNetCore/Configuration/NullAttributeResponseBehavior.cs @@ -1,19 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Text; - namespace JsonApiDotNetCore.Configuration { + /// + /// Allows null attributes to be ommitted from the response payload + /// public struct NullAttributeResponseBehavior { + /// Do not serialize null attributes + /// + /// Allow clients to override the serialization behavior through a query parmeter. + /// + /// ``` + /// GET /articles?omitNullValuedAttributes=true + /// ``` + /// + /// public NullAttributeResponseBehavior(bool omitNullValuedAttributes = false, bool allowClientOverride = false) { OmitNullValuedAttributes = omitNullValuedAttributes; AllowClientOverride = allowClientOverride; } + /// + /// Do not include null attributes in the response payload. + /// public bool OmitNullValuedAttributes { get; } + + /// + /// Allows clients to specify a `omitNullValuedAttributes` boolean query param to control + /// serialization behavior. + /// public bool AllowClientOverride { get; } - // ... } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs b/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs index 6fff3a22c8..8c3099dde1 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs @@ -5,13 +5,8 @@ namespace JsonApiDotNetCore.Controllers { - public abstract class JsonApiControllerMixin : Controller + public abstract class JsonApiControllerMixin : ControllerBase { - protected IActionResult UnprocessableEntity() - { - return new StatusCodeResult(422); - } - protected IActionResult Forbidden() { return new StatusCodeResult(403); @@ -23,18 +18,19 @@ protected IActionResult Error(Error error) { Errors = new List { error } }; - var result = new ObjectResult(errorCollection); - result.StatusCode = error.StatusCode; - return result; + return new ObjectResult(errorCollection) + { + StatusCode = error.StatusCode + }; } protected IActionResult Errors(ErrorCollection errors) { - var result = new ObjectResult(errors); - result.StatusCode = GetErrorStatusCode(errors); - - return result; + return new ObjectResult(errors) + { + StatusCode = GetErrorStatusCode(errors) + }; } private int GetErrorStatusCode(ErrorCollection errors) diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs index c3b667bc7d..f6db9f0d06 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Controllers /// /// A controller to be used for bulk operations as defined in the json:api 1.1 specification /// - public class JsonApiOperationsController : Controller + public class JsonApiOperationsController : ControllerBase { private readonly IOperationsProcessor _operationsProcessor; diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 807d21c018..5e8eeefdd1 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -20,23 +20,23 @@ namespace JsonApiDotNetCore.Extensions // ReSharper disable once InconsistentNaming public static class IServiceCollectionExtensions { - public static void AddJsonApi(this IServiceCollection services) + public static IServiceCollection AddJsonApi(this IServiceCollection services) where TContext : DbContext { - var mvcBuilder = services.AddMvc(); - AddJsonApi(services, (opt) => { }, mvcBuilder); + var mvcBuilder = services.AddMvcCore(); + return AddJsonApi(services, opt => { }, mvcBuilder); } - public static void AddJsonApi(this IServiceCollection services, Action options) + public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options) where TContext : DbContext { - var mvcBuilder = services.AddMvc(); - AddJsonApi(services, options, mvcBuilder); + var mvcBuilder = services.AddMvcCore(); + return AddJsonApi(services, options, mvcBuilder); } - public static void AddJsonApi(this IServiceCollection services, + public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options, - IMvcBuilder mvcBuilder) where TContext : DbContext + IMvcCoreBuilder mvcBuilder) where TContext : DbContext { var config = new JsonApiOptions(); @@ -52,11 +52,12 @@ public static void AddJsonApi(this IServiceCollection services, }); AddJsonApiInternals(services, config); + return services; } - public static void AddJsonApi(this IServiceCollection services, + public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options, - IMvcBuilder mvcBuilder) + IMvcCoreBuilder mvcBuilder) { var config = new JsonApiOptions(); @@ -70,6 +71,7 @@ public static void AddJsonApi(this IServiceCollection services, }); AddJsonApiInternals(services, config); + return services; } public static void AddJsonApiInternals( diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 4f52a23002..7019a8e6cc 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,6 +1,6 @@  - 2.2.5 + 2.3.1 $(NetStandardVersion) JsonApiDotNetCore JsonApiDotNetCore @@ -17,12 +17,13 @@ - + - + - + + diff --git a/src/JsonApiDotNetCore/Models/AttrAttribute.cs b/src/JsonApiDotNetCore/Models/AttrAttribute.cs index db61cb56ea..f63d48ab70 100644 --- a/src/JsonApiDotNetCore/Models/AttrAttribute.cs +++ b/src/JsonApiDotNetCore/Models/AttrAttribute.cs @@ -5,6 +5,26 @@ namespace JsonApiDotNetCore.Models { public class AttrAttribute : Attribute { + /// + /// Defines a public attribute exposed by the API + /// + /// + /// How this attribute is exposed through the API + /// Prevent PATCH requests from updating the value + /// Prevent filters on this attribute + /// Prevent this attribute from being sorted by + /// + /// + /// + /// + /// public class Author : Identifiable + /// { + /// [Attr("name")] + /// public string Name { get; set; } + /// } + /// + /// + /// public AttrAttribute(string publicName, bool isImmutable = false, bool isFilterable = true, bool isSortable = true) { PublicAttributeName = publicName; @@ -13,27 +33,58 @@ public AttrAttribute(string publicName, bool isImmutable = false, bool isFiltera IsSortable = isSortable; } - internal AttrAttribute(string publicName, string internalName, bool isImmutable = false) + public AttrAttribute(string publicName, string internalName, bool isImmutable = false) { PublicAttributeName = publicName; InternalAttributeName = internalName; IsImmutable = isImmutable; } + /// + /// How this attribute is exposed through the API + /// public string PublicAttributeName { get; } + + /// + /// The internal property name this attribute belongs to. + /// public string InternalAttributeName { get; internal set; } + + /// + /// Prevents PATCH requests from updating the value. + /// public bool IsImmutable { get; } + + /// + /// Whether or not this attribute can be filtered on via a query string filters. + /// Attempts to filter on an attribute with `IsFilterable == false` will return + /// an HTTP 400 response. + /// public bool IsFilterable { get; } + + /// + /// Whether or not this attribute can be sorted on via a query string sort. + /// Attempts to filter on an attribute with `IsSortable == false` will return + /// an HTTP 400 response. + /// public bool IsSortable { get; } + /// + /// Get the value of the attribute for the given object. + /// Returns null if the attribute does not belong to the + /// provided object. + /// public object GetValue(object entity) { return entity .GetType() .GetProperty(InternalAttributeName) - .GetValue(entity); + ?.GetValue(entity); } + /// + /// Sets the value of the attribute on the given object. + /// public void SetValue(object entity, object newValue) { var propertyInfo = entity diff --git a/src/JsonApiDotNetCore/Models/HasManyAttribute.cs b/src/JsonApiDotNetCore/Models/HasManyAttribute.cs index c2d7594400..877df29146 100644 --- a/src/JsonApiDotNetCore/Models/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Models/HasManyAttribute.cs @@ -2,16 +2,40 @@ namespace JsonApiDotNetCore.Models { public class HasManyAttribute : RelationshipAttribute { + /// + /// Create a HasMany relational link to another entity + /// + /// + /// The relationship name as exposed by the API + /// Which links are available. Defaults to + /// Whether or not this relationship can be included using the ?include=public-name query string + /// + /// + /// + /// + /// public class Author : Identifiable + /// { + /// [HasMany("articles"] + /// public virtual List
Articles { get; set; } + /// } + /// + /// + /// public HasManyAttribute(string publicName, Link documentLinks = Link.All, bool canInclude = true) : base(publicName, documentLinks, canInclude) { } + /// + /// Sets the value of the property identified by this attribute + /// + /// The target object + /// The new property value public override void SetValue(object entity, object newValue) { var propertyInfo = entity .GetType() .GetProperty(InternalRelationshipName); - + propertyInfo.SetValue(entity, newValue); } } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index 20d119ff07..500101cc62 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -65,7 +65,11 @@ private string GetErrorJson(object responseObject, ILogger logger) } else { - logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); + if (logger?.IsEnabled(LogLevel.Information) == true) + { + logger.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); + } + return JsonConvert.SerializeObject(responseObject); } } diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 642ee00a57..a3094eb8f9 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -89,8 +89,10 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh if (relationshipName == null) throw new JsonApiException(422, "Relationship name not specified."); - - _logger.LogTrace($"Looking up '{relationshipName}'..."); + if (_logger.IsEnabled(LogLevel.Trace)) + { + _logger.LogTrace($"Looking up '{relationshipName}'..."); + } var entity = await _entities.GetAndIncludeAsync(id, relationshipName); // TODO: it would be better if we could distinguish whether or not the relationship was not found, @@ -166,7 +168,10 @@ protected virtual async Task> ApplyPageQueryAsync(IQueryable e if (!pageManager.IsPaginated) return await _entities.ToListAsync(entities); - _logger?.LogInformation($"Applying paging query. Fetching page {pageManager.CurrentPage} with {pageManager.PageSize} entities"); + if (_logger?.IsEnabled(LogLevel.Information) == true) + { + _logger?.LogInformation($"Applying paging query. Fetching page {pageManager.CurrentPage} with {pageManager.PageSize} entities"); + } return await _entities.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); } diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index 41bc64151b..dc9ff7ef0a 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -45,7 +45,11 @@ public bool TryGetValue(string key, out T value) var stringValue = GetFilterValue(key); if (stringValue == null) { - _logger.LogInformation($"'{key}' was not found in the query collection"); + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation($"'{key}' was not found in the query collection"); + } + return false; } @@ -56,7 +60,12 @@ public bool TryGetValue(string key, out T value) } catch (FormatException) { - _logger.LogInformation($"'{value}' is not a valid '{typeof(T).Name}' value for query parameter {key}"); + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation( + $"'{value}' is not a valid '{typeof(T).Name}' value for query parameter {key}"); + } + return false; } } diff --git a/src/JsonApiDotNetCore/Services/QueryComposer.cs b/src/JsonApiDotNetCore/Services/QueryComposer.cs index 8e0819a438..e365811704 100644 --- a/src/JsonApiDotNetCore/Services/QueryComposer.cs +++ b/src/JsonApiDotNetCore/Services/QueryComposer.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using JsonApiDotNetCore.Internal.Query; -using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Services { @@ -14,8 +13,8 @@ public class QueryComposer : IQueryComposer public string Compose(IJsonApiContext jsonApiContext) { string result = ""; - if(jsonApiContext != null && jsonApiContext.QuerySet != null) - { + if (jsonApiContext != null && jsonApiContext.QuerySet != null) + { List filterQueries = jsonApiContext.QuerySet.Filters; if (filterQueries.Count > 0) { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs index ce2b541f5b..fcc6e5ffde 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs @@ -11,7 +11,7 @@ public class CustomErrorTests public void Can_Return_Custom_Error_Types() { // arrange - var error = new CustomError("507", "title", "detail", "custom"); + var error = new CustomError(507, "title", "detail", "custom"); var errorCollection = new ErrorCollection(); errorCollection.Add(error); @@ -36,7 +36,7 @@ public void Can_Return_Custom_Error_Types() } class CustomError : Error { - public CustomError(string status, string title, string detail, string myProp) + public CustomError(int status, string title, string detail, string myProp) : base(status, title, detail) { MyCustomProperty = myProp; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs index 428e3cadf3..b928df4ada 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs @@ -131,7 +131,7 @@ public async Task Can_Filter_On_Not_Equal_Values() // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.False(deserializedTodoItems.Any(i => i.Ordinal == todoItem.Ordinal)); + Assert.DoesNotContain(deserializedTodoItems, x => x.Ordinal == todoItem.Ordinal); } [Fact] @@ -170,8 +170,8 @@ public async Task Can_Filter_On_In_Array_Values() Assert.Equal(guids.Count(), deserializedTodoItems.Count()); foreach (var item in deserializedTodoItems) { - Assert.True(guids.Contains(item.GuidProperty)); - Assert.False(notInGuids.Contains(item.GuidProperty)); + Assert.Contains(item.GuidProperty, guids); + Assert.DoesNotContain(item.GuidProperty, notInGuids); } } @@ -207,7 +207,7 @@ public async Task Can_Filter_On_Related_In_Array_Values() Assert.NotNull(included); Assert.NotEmpty(included); foreach (var item in included) - Assert.True(ownerFirstNames.Contains(item.Attributes["first-name"])); + Assert.Contains(item.Attributes["first-name"], ownerFirstNames); } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 2c71275473..015d79cdbd 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -106,7 +106,7 @@ public async Task Cannot_Create_Entity_With_Client_Generate_Id() attributes = new { description = todoItem.Description, - ordinal = todoItem.Ordinal, + ordinal = todoItem.Ordinal, createdDate = DateTime.Now } } @@ -174,7 +174,7 @@ public async Task Can_Create_Guid_Identifiable_Entity_With_Client_Defined_Id_If_ var httpMethod = new HttpMethod("POST"); var server = new TestServer(builder); var client = server.CreateClient(); - + var context = _fixture.GetService(); var owner = new JsonApiDotNetCoreExample.Models.Person(); @@ -285,6 +285,63 @@ public async Task Can_Create_And_Set_HasMany_Relationships() Assert.NotEmpty(contextCollection.TodoItems); } + [Fact] + public async Task Can_Create_And_Set_HasOne_Relationships() + { + // arrange + var builder = new WebHostBuilder() + .UseStartup(); + var httpMethod = new HttpMethod("POST"); + var server = new TestServer(builder); + var client = server.CreateClient(); + + var context = _fixture.GetService(); + + var todoItem = new TodoItem(); + var owner = new JsonApiDotNetCoreExample.Models.Person(); + context.People.Add(owner); + await context.SaveChangesAsync(); + + var route = "/api/v1/todo-items"; + var request = new HttpRequestMessage(httpMethod, route); + var content = new + { + data = new + { + type = "todo-items", + relationships = new Dictionary + { + { "owner", new { + data = new + { + type = "people", + id = owner.Id.ToString() + } + } } + } + } + }; + + request.Content = new StringContent(JsonConvert.SerializeObject(content)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + + // act + var response = await client.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + + // assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var newId = deserializedBody.Id; + + context = _fixture.GetService(); + var todoItemResult = context.TodoItems + .Include(c => c.Owner) + .SingleOrDefault(c => c.Id == newId); + + Assert.Equal(owner.Id, todoItemResult.OwnerId); + } + [Fact] public async Task ShouldReceiveLocationHeader_InResponse() { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs index 343526f7d8..25f81fb66c 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net; using System.Net.Http; @@ -174,7 +174,7 @@ public async Task GET_Included_DoesNot_Duplicate_Records_ForMultipleRelationship // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotEmpty(documents.Included); - Assert.Equal(1, documents.Included.Count); + Assert.Single(documents.Included); } [Fact] @@ -209,7 +209,7 @@ public async Task GET_Included_DoesNot_Duplicate_Records_If_HasOne_Exists_Twice( // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotEmpty(documents.Included); - Assert.Equal(1, documents.Included.Count); + Assert.Single(documents.Included); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/PagingTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/PagingTests.cs index faae94a3d8..e50032fce1 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/PagingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/PagingTests.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using JsonApiDotNetCoreExample; @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.TestHost; using Newtonsoft.Json; using Xunit; -using Person = JsonApiDotNetCoreExample.Models.Person; using JsonApiDotNetCore.Models; using JsonApiDotNetCoreExample.Data; using Bogus; @@ -18,28 +17,16 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests [Collection("WebHostCollection")] public class PagingTests { - private TestFixture _fixture; - private AppDbContext _context; - private Faker _personFaker; - private Faker _todoItemFaker; - private Faker _todoItemCollectionFaker; - private DateTime CurrentTime; + private readonly AppDbContext _context; + private readonly Faker _todoItemFaker; public PagingTests(TestFixture fixture) { - _fixture = fixture; _context = fixture.GetService(); - _personFaker = new Faker() - .RuleFor(p => p.FirstName, f => f.Name.FirstName()) - .RuleFor(p => p.LastName, f => f.Name.LastName()); - _todoItemFaker = new Faker() .RuleFor(t => t.Description, f => f.Lorem.Sentence()) .RuleFor(t => t.Ordinal, f => f.Random.Number()) .RuleFor(t => t.CreatedDate, f => f.Date.Past()); - - _todoItemCollectionFaker = new Faker() - .RuleFor(t => t.Name, f => f.Company.CatchPhrase()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs index f5f61e156d..58b2323da5 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs @@ -39,7 +39,7 @@ public async Task Server_Returns_400_ForUnknownQueryParam() // assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Equal(1, body.Errors.Count); + Assert.Single(body.Errors); Assert.Equal($"[{queryKey}, {queryValue}] is not a valid query.", body.Errors[0].Title); } } diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/IQueryableExtensions.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/IQueryableExtensions.cs index c774478227..a7bfb95b9d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/IQueryableExtensions.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/IQueryableExtensions.cs @@ -12,38 +12,31 @@ namespace JsonApiDotNetCoreExampleTests.Helpers.Extensions { public static class IQueryableExtensions { - private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo(); - - private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler"); + private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.Single(x => x.Name == "_queryCompiler"); - private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider"); + private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo(); - private static readonly MethodInfo CreateQueryParserMethod = QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == "CreateQueryParser"); + private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_queryModelGenerator"); - private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database"); + private static readonly FieldInfo DatabaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database"); - private static readonly PropertyInfo DatabaseDependenciesField - = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies"); + private static readonly PropertyInfo DependenciesProperty = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies"); - public static string ToSql(this IQueryable query) where TEntity : class + public static string ToSql(this IQueryable queryable) + where TEntity : class { - if (!(query is EntityQueryable) && !(query is InternalDbSet)) - { - throw new ArgumentException("Invalid query"); - } - - var queryCompiler = (IQueryCompiler)QueryCompilerField.GetValue(query.Provider); - var nodeTypeProvider = (INodeTypeProvider)NodeTypeProviderField.GetValue(queryCompiler); - var parser = (IQueryParser)CreateQueryParserMethod.Invoke(queryCompiler, new object[] { nodeTypeProvider }); - var queryModel = parser.GetParsedQuery(query.Expression); - var database = DataBaseField.GetValue(queryCompiler); - var queryCompilationContextFactory = ((DatabaseDependencies)DatabaseDependenciesField.GetValue(database)).QueryCompilationContextFactory; + if (!(queryable is EntityQueryable) && !(queryable is InternalDbSet)) + throw new ArgumentException(); + + var queryCompiler = (IQueryCompiler)QueryCompilerField.GetValue(queryable.Provider); + var queryModelGenerator = (IQueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler); + var queryModel = queryModelGenerator.ParseQuery(queryable.Expression); + var database = DatabaseField.GetValue(queryCompiler); + var queryCompilationContextFactory = ((DatabaseDependencies)DependenciesProperty.GetValue(database)).QueryCompilationContextFactory; var queryCompilationContext = queryCompilationContextFactory.Create(false); var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor(); modelVisitor.CreateQueryExecutor(queryModel); - var sql = modelVisitor.Queries.First().ToString(); - - return sql; + return modelVisitor.Queries.Join(Environment.NewLine + Environment.NewLine); } } -} \ No newline at end of file +} diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/StringExtensions.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/StringExtensions.cs index 19c7491d2a..dbe6b19feb 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/StringExtensions.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/StringExtensions.cs @@ -2,14 +2,14 @@ namespace JsonApiDotNetCoreExampleTests.Helpers.Extensions { - public static class StringExtensions { public static string Normalize(this string input) { return Regex.Replace(input, @"\s+", string.Empty) .ToUpper() - .Replace('"', '\''); + .Replace("\"", string.Empty) + .Replace("'", string.Empty); } } -} \ No newline at end of file +} diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs index 0001878a50..6bc5a08016 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs @@ -20,25 +20,18 @@ public MetaStartup(IHostingEnvironment env) public override IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); - - loggerFactory - .AddConsole(LogLevel.Trace); - - services.AddSingleton(loggerFactory); - - services.AddDbContext(options => - { - options.UseNpgsql(GetDbConnectionString()); - }, ServiceLifetime.Transient); - - services.AddJsonApi(opt => - { - opt.Namespace = "api/v1"; - opt.DefaultPageSize = 5; - opt.IncludeTotalRecordCount = true; - }); - - services.AddScoped(); + loggerFactory.AddConsole(LogLevel.Warning); + + services + .AddSingleton(loggerFactory) + .AddDbContext(options => + options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) + .AddJsonApi(options => { + options.Namespace = "api/v1"; + options.DefaultPageSize = 5; + options.IncludeTotalRecordCount = true; + }) + .AddScoped(); return services.BuildServiceProvider(); } diff --git a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj index b1df354bf7..c0c410f7c7 100755 --- a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +++ b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj @@ -13,21 +13,20 @@ - + - + - + - diff --git a/test/JsonApiDotNetCoreExampleTests/appsettings.json b/test/JsonApiDotNetCoreExampleTests/appsettings.json index e65cee8d0d..c468439079 100644 --- a/test/JsonApiDotNetCoreExampleTests/appsettings.json +++ b/test/JsonApiDotNetCoreExampleTests/appsettings.json @@ -5,9 +5,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Error", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" } } } diff --git a/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj b/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj index f1d4044e0b..c0500c3bbd 100644 --- a/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj +++ b/test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj @@ -17,11 +17,11 @@ - + - + diff --git a/test/NoEntityFrameworkTests/appsettings.json b/test/NoEntityFrameworkTests/appsettings.json index 01f85e6699..c468439079 100644 --- a/test/NoEntityFrameworkTests/appsettings.json +++ b/test/NoEntityFrameworkTests/appsettings.json @@ -5,9 +5,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" } } } diff --git a/test/OperationsExampleTests/Add/AddTests.cs b/test/OperationsExampleTests/Add/AddTests.cs index 39e9e8a2a6..47aba439d6 100644 --- a/test/OperationsExampleTests/Add/AddTests.cs +++ b/test/OperationsExampleTests/Add/AddTests.cs @@ -156,7 +156,7 @@ public async Task Can_Create_Author_With_Article() Assert.Equal(author.Name, lastAuthor.Name); // article validation - Assert.Equal(1, lastAuthor.Articles.Count); + Assert.Single(lastAuthor.Articles); Assert.Equal(article.Name, lastAuthor.Articles[0].Name); Assert.Equal(articleOperationResult.DataObject.Id, lastAuthor.Articles[0].StringId); } diff --git a/test/OperationsExampleTests/Get/GetByIdTests.cs b/test/OperationsExampleTests/Get/GetByIdTests.cs index 1dee2867ce..1056082895 100644 --- a/test/OperationsExampleTests/Get/GetByIdTests.cs +++ b/test/OperationsExampleTests/Get/GetByIdTests.cs @@ -36,14 +36,14 @@ public async Task Can_Get_Author_By_Id() }; // act - var result = await PatchAsync("api/bulk", content); + var (response, data) = await PatchAsync("api/bulk", content); // assert - Assert.NotNull(result.response); - Assert.NotNull(result.data); - Assert.Equal(HttpStatusCode.OK, result.response.StatusCode); - Assert.Equal(1, result.data.Operations.Count); - Assert.Equal(author.Id.ToString(), result.data.Operations.Single().DataObject.Id); + Assert.NotNull(response); + Assert.NotNull(data); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Single(data.Operations); + Assert.Equal(author.Id.ToString(), data.Operations.Single().DataObject.Id); } [Fact] @@ -69,7 +69,7 @@ public async Task Get_Author_By_Id_Returns_404_If_NotFound() Assert.NotNull(response); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.NotNull(data); - Assert.Equal(1, data.Errors.Count); + Assert.Single(data.Errors); Assert.True(data.Errors[0].Detail.Contains("authors"), "The error detail should contain the name of the entity that could not be found."); Assert.True(data.Errors[0].Detail.Contains(authorId), "The error detail should contain the entity id that could not be found"); Assert.True(data.Errors[0].Title.Contains("operation[0]"), "The error title should contain the operation identifier that failed"); diff --git a/test/OperationsExampleTests/Get/GetRelationshipTests.cs b/test/OperationsExampleTests/Get/GetRelationshipTests.cs index 03f276da17..8599e92a1e 100644 --- a/test/OperationsExampleTests/Get/GetRelationshipTests.cs +++ b/test/OperationsExampleTests/Get/GetRelationshipTests.cs @@ -43,7 +43,7 @@ public async Task Can_Get_Article_Author() Assert.NotNull(response); Assert.NotNull(data); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(1, data.Operations.Count); + Assert.Single(data.Operations); var resourceObject = data.Operations.Single().DataObject; Assert.Equal(author.Id.ToString(), resourceObject.Id); Assert.Equal("authors", resourceObject.Type); diff --git a/test/OperationsExampleTests/Get/GetTests.cs b/test/OperationsExampleTests/Get/GetTests.cs index bf1c15e3a4..78e7eeb976 100644 --- a/test/OperationsExampleTests/Get/GetTests.cs +++ b/test/OperationsExampleTests/Get/GetTests.cs @@ -44,7 +44,7 @@ public async Task Can_Get_Authors() Assert.NotNull(result.response); Assert.NotNull(result.data); Assert.Equal(HttpStatusCode.OK, result.response.StatusCode); - Assert.Equal(1, result.data.Operations.Count); + Assert.Single(result.data.Operations); Assert.Equal(expectedCount, result.data.Operations.Single().DataList.Count); } } diff --git a/test/OperationsExampleTests/OperationsExampleTests.csproj b/test/OperationsExampleTests/OperationsExampleTests.csproj index 13f68faf5a..c2c70d6e54 100644 --- a/test/OperationsExampleTests/OperationsExampleTests.csproj +++ b/test/OperationsExampleTests/OperationsExampleTests.csproj @@ -10,11 +10,11 @@ - + - - + + diff --git a/test/OperationsExampleTests/Transactions/TransactionFailureTests.cs b/test/OperationsExampleTests/Transactions/TransactionFailureTests.cs index 05dea7f29d..191711651d 100644 --- a/test/OperationsExampleTests/Transactions/TransactionFailureTests.cs +++ b/test/OperationsExampleTests/Transactions/TransactionFailureTests.cs @@ -68,7 +68,7 @@ public async Task Cannot_Create_Author_If_Article_Creation_Fails() // for now, it is up to application implementations to perform validation and // provide the proper HTTP response code Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); - Assert.Equal(1, data.Errors.Count); + Assert.Single(data.Errors); Assert.Contains("operation[1] (add)", data.Errors[0].Title); var dbAuthors = await context.Authors.Where(a => a.Name == author.Name).ToListAsync(); diff --git a/test/OperationsExampleTests/Update/UpdateTests.cs b/test/OperationsExampleTests/Update/UpdateTests.cs index c5d0f20900..c5d220b2f5 100644 --- a/test/OperationsExampleTests/Update/UpdateTests.cs +++ b/test/OperationsExampleTests/Update/UpdateTests.cs @@ -52,7 +52,7 @@ public async Task Can_Update_Author() Assert.NotNull(response); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(data); - Assert.Equal(1, data.Operations.Count); + Assert.Single(data.Operations); var attrs = data.Operations.Single().DataObject.Attributes; Assert.Equal(updates.Name, attrs["name"]); diff --git a/test/OperationsExampleTests/appsettings.json b/test/OperationsExampleTests/appsettings.json index 0d77cd40f2..73030b1743 100644 --- a/test/OperationsExampleTests/appsettings.json +++ b/test/OperationsExampleTests/appsettings.json @@ -5,9 +5,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Trace", - "System": "Trace", - "Microsoft": "Trace" + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" } } } diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index fc816765eb..84c1f3f5c7 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -190,7 +190,7 @@ public void Build_Can_Build_Arrays() var documents = documentBuilder.Build(entities); - Assert.Equal(1, documents.Data.Count); + Assert.Single(documents.Data); } [Fact] @@ -201,7 +201,7 @@ public void Build_Can_Build_CustomIEnumerables() var documents = documentBuilder.Build(entities); - Assert.Equal(1, documents.Data.Count); + Assert.Single(documents.Data); } diff --git a/test/UnitTests/Controllers/JsonApiControllerMixin_Tests.cs b/test/UnitTests/Controllers/JsonApiControllerMixin_Tests.cs index a18aeaf668..c7a4a6cb4d 100644 --- a/test/UnitTests/Controllers/JsonApiControllerMixin_Tests.cs +++ b/test/UnitTests/Controllers/JsonApiControllerMixin_Tests.cs @@ -16,26 +16,26 @@ public void Errors_Correctly_Infers_Status_Code() // arrange var errors422 = new ErrorCollection { Errors = new List { - new Error("422", "bad specific"), - new Error("422", "bad other specific"), + new Error(422, "bad specific"), + new Error(422, "bad other specific"), } }; var errors400 = new ErrorCollection { Errors = new List { - new Error("200", "weird"), - new Error("400", "bad"), - new Error("422", "bad specific"), + new Error(200, "weird"), + new Error(400, "bad"), + new Error(422, "bad specific"), } }; var errors500 = new ErrorCollection { Errors = new List { - new Error("200", "weird"), - new Error("400", "bad"), - new Error("422", "bad specific"), - new Error("500", "really bad"), - new Error("502", "really bad specific"), + new Error(200, "weird"), + new Error(400, "bad"), + new Error(422, "bad specific"), + new Error(500, "really bad"), + new Error(502, "really bad specific"), } }; diff --git a/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs b/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs index ce8316b89d..5f56ac0bd8 100644 --- a/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs +++ b/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs @@ -33,7 +33,7 @@ public void Adding_DbContext_Members_That_DoNot_Implement_IIdentifiable_Creates_ var contextGraph = contextGraphBuilder.Build() as ContextGraph; // assert - Assert.Equal(1, contextGraph.ValidationResults.Count); + Assert.Single(contextGraph.ValidationResults); Assert.Contains(contextGraph.ValidationResults, v => v.LogLevel == LogLevel.Warning); } diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index ec34d87d24..be16d1d9d7 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -181,7 +181,7 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() // assert Assert.NotNull(result.ComplexMember); - Assert.Equal(1, attributesToUpdate.Count); + Assert.Single(attributesToUpdate); foreach (var attr in attributesToUpdate) Assert.False(attr.Key.IsImmutable); diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index a6ed346e7d..f8dde36f41 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -4,12 +4,12 @@ false - - + + - +