diff --git a/.vscode/launch.json b/.vscode/launch.json index 4dfa71e085..b26b008078 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,41 +1,11 @@ { "version": "0.2.0", "configurations": [ - { - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceRoot}/src/JsonApiDotNetCoreExample/bin/Debug/netcoreapp1.0/JsonApiDotNetCoreExample.dll", - "args": [], - "cwd": "${workspaceRoot}/src/JsonApiDotNetCoreExample", - "stopAtEntry": false, - "launchBrowser": { - "enabled": false, - "args": "${auto-detect-url}", - "windows": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}" - }, - "osx": { - "command": "open" - }, - "linux": { - "command": "xdg-open" - } - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceRoot}/Views" - } - }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", - "processId": "${command.pickProcess}" + "processId": "${command:pickProcess}" } ] } \ No newline at end of file diff --git a/README.md b/README.md index f7b068f988..29b8bff318 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,14 @@ Install-Package JsonApiDotnetCore - project.json ```json -"JsonApiDotNetCore": "1.1.0" +"JsonApiDotNetCore": "1.2.0" ``` - *.csproj ```xml - + ``` @@ -326,6 +326,10 @@ Resources can be sorted by an attribute: ### Meta +Meta objects can be assigned in two ways: + - Resource meta + - Request Meta + Resource meta can be defined by implementing `IHasMeta` on the model class: ```csharp @@ -343,6 +347,9 @@ public class Person : Identifiable, IHasMeta } ``` +Request Meta can be added by injecting a service that implements `IRequestMeta`. +In the event of a key collision, the Request Meta will take precendence. + ### Client Generated Ids By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 2df9eaca4f..63829021d7 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -8,10 +8,11 @@ namespace JsonApiDotNetCore.Builders { - public class DocumentBuilder + public class DocumentBuilder : IDocumentBuilder { private IJsonApiContext _jsonApiContext; private IContextGraph _contextGraph; + private readonly IRequestMeta _requestMeta; public DocumentBuilder(IJsonApiContext jsonApiContext) { @@ -19,6 +20,13 @@ public DocumentBuilder(IJsonApiContext jsonApiContext) _contextGraph = jsonApiContext.ContextGraph; } + public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta) + { + _jsonApiContext = jsonApiContext; + _contextGraph = jsonApiContext.ContextGraph; + _requestMeta = requestMeta; + } + public Document Build(IIdentifiable entity) { var contextEntity = _contextGraph.GetContextEntity(entity.GetType()); @@ -62,16 +70,19 @@ public Documents Build(IEnumerable entities) private Dictionary _getMeta(IIdentifiable entity) { if (entity == null) return null; - - var meta = new Dictionary(); - var metaEntity = entity as IHasMeta; - if(metaEntity != null) - meta = metaEntity.GetMeta(_jsonApiContext); + var builder = _jsonApiContext.MetaBuilder; + + if(entity is IHasMeta metaEntity) + builder.Add(metaEntity.GetMeta(_jsonApiContext)); if(_jsonApiContext.Options.IncludeTotalRecordCount) - meta["total-records"] = _jsonApiContext.PageManager.TotalRecords; + builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords); + if(_requestMeta != null) + builder.Add(_requestMeta.GetMeta()); + + var meta = builder.Build(); if(meta.Count > 0) return meta; return null; } diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs new file mode 100644 index 0000000000..8fe5c65ae9 --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Builders +{ + public interface IDocumentBuilder + { + Document Build(IIdentifiable entity); + Documents Build(IEnumerable entities); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Builders/IMetaBuilder.cs b/src/JsonApiDotNetCore/Builders/IMetaBuilder.cs new file mode 100644 index 0000000000..bf35b9d210 --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/IMetaBuilder.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace JsonApiDotNetCore.Builders +{ + public interface IMetaBuilder + { + void Add(string key, object value); + void Add(Dictionary values); + Dictionary Build(); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Builders/MetaBuilder.cs b/src/JsonApiDotNetCore/Builders/MetaBuilder.cs new file mode 100644 index 0000000000..14b80321f6 --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/MetaBuilder.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; + +namespace JsonApiDotNetCore.Builders +{ + public class MetaBuilder : IMetaBuilder + { + private Dictionary _meta = new Dictionary(); + + public void Add(string key, object value) + { + _meta[key] = value; + } + + /// + /// Joins the new dictionary with the current one. In the event of a key collision, + /// the new value will override the old. + /// + public void Add(Dictionary values) + { + _meta = values.Keys.Union(_meta.Keys) + .ToDictionary(key => key, + key => values.ContainsKey(key) ? values[key] : _meta[key]); + } + + public Dictionary Build() + { + return _meta; + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 5a44bb076c..4c98f8cec8 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -32,6 +32,7 @@ public class DefaultEntityRepository private readonly DbSet _dbSet; private readonly ILogger _logger; private readonly IJsonApiContext _jsonApiContext; + private readonly IGenericProcessorFactory _genericProcessorFactory; public DefaultEntityRepository( DbContext context, @@ -42,6 +43,7 @@ public DefaultEntityRepository( _dbSet = context.GetDbSet(); _jsonApiContext = jsonApiContext; _logger = loggerFactory.CreateLogger>(); + _genericProcessorFactory = _jsonApiContext.GenericProcessorFactory; } public virtual IQueryable Get() @@ -110,7 +112,7 @@ public virtual async Task UpdateAsync(TId id, TEntity entity) public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds) { - var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.Type, _context); + var genericProcessor = _genericProcessorFactory.GetProcessor(relationship.Type); await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds); } diff --git a/src/JsonApiDotNetCore/Extensions/ServiceProviderExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs similarity index 79% rename from src/JsonApiDotNetCore/Extensions/ServiceProviderExtensions.cs rename to src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 38face7307..1e646c085d 100644 --- a/src/JsonApiDotNetCore/Extensions/ServiceProviderExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -1,8 +1,10 @@ using System; +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Formatters; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -11,7 +13,7 @@ namespace JsonApiDotNetCore.Extensions { - public static class ServiceProviderExtensions + public static class IServiceCollectionExtensions { public static void AddJsonApi(this IServiceCollection services) where TContext : DbContext @@ -54,6 +56,15 @@ public static void AddJsonApiInternals(this IServiceCollection service services.AddSingleton(); services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(typeof(GenericProcessor<>)); } public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions jsonApiOptions) diff --git a/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs new file mode 100644 index 0000000000..5b64cc42bf --- /dev/null +++ b/src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace JsonApiDotNetCore.Formatters +{ + public interface IJsonApiReader + { + Task ReadAsync(InputFormatterContext context); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Formatters/IJsonApiWriter.cs b/src/JsonApiDotNetCore/Formatters/IJsonApiWriter.cs new file mode 100644 index 0000000000..ce8b7da6a4 --- /dev/null +++ b/src/JsonApiDotNetCore/Formatters/IJsonApiWriter.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace JsonApiDotNetCore.Formatters +{ + public interface IJsonApiWriter + { + Task WriteAsync(OutputFormatterWriteContext context); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs index c4f1692eaa..12e57deadf 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs @@ -1,13 +1,7 @@ using System; -using System.IO; using System.Threading.Tasks; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Microsoft.EntityFrameworkCore; namespace JsonApiDotNetCore.Formatters { @@ -23,55 +17,10 @@ public bool CanRead(InputFormatterContext context) return contentTypeString == "application/vnd.api+json"; } - 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); - } - - var loggerFactory = GetService(context); - var logger = loggerFactory?.CreateLogger(); - - var dbContext = GetService(context); - - try - { - var body = GetRequestBody(context.HttpContext.Request.Body); - var jsonApiContext = GetService(context); - var model = jsonApiContext.IsRelationshipPath ? - JsonApiDeSerializer.DeserializeRelationship(body, jsonApiContext) : - JsonApiDeSerializer.Deserialize(body, jsonApiContext, dbContext); - - if(model == null) - logger?.LogError("An error occurred while de-serializing the payload"); - - return InputFormatterResult.SuccessAsync(model); - } - catch (JsonSerializationException ex) - { - logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload"); - context.HttpContext.Response.StatusCode = 422; - return InputFormatterResult.FailureAsync(); - } - } - - private string GetRequestBody(Stream body) - { - using (var reader = new StreamReader(body)) - { - return reader.ReadToEnd(); - } - } - - private TService GetService(InputFormatterContext context) - { - return context.HttpContext.RequestServices.GetService(); + var reader = context.HttpContext.RequestServices.GetService(); + return await reader.ReadAsync(context); } } } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs index 95e607fca5..2431055d1d 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs @@ -1,14 +1,7 @@ using System; -using System.Text; using System.Threading.Tasks; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace JsonApiDotNetCore.Formatters { @@ -26,76 +19,8 @@ public bool CanWriteResult(OutputFormatterCanWriteContext context) public async Task WriteAsync(OutputFormatterWriteContext context) { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - var logger = GetService(context)? - .CreateLogger(); - - logger?.LogInformation("Formatting response as JSONAPI"); - - var response = context.HttpContext.Response; - using (var writer = context.WriterFactory(response.Body, Encoding.UTF8)) - { - var jsonApiContext = GetService(context); - - response.ContentType = "application/vnd.api+json"; - string responseContent; - try - { - responseContent = GetResponseBody(context.Object, jsonApiContext, logger); - } - catch (Exception e) - { - logger?.LogError(new EventId(), e, "An error ocurred while formatting the response"); - var errors = new ErrorCollection(); - errors.Add(new Error("400", e.Message)); - responseContent = errors.GetJson(); - response.StatusCode = 400; - } - - await writer.WriteAsync(responseContent); - await writer.FlushAsync(); - } - } - - private T GetService(OutputFormatterWriteContext context) - { - return context.HttpContext.RequestServices.GetService(); - } - - private string GetResponseBody(object responseObject, IJsonApiContext jsonApiContext, ILogger logger) - { - if (responseObject == null) - return GetNullDataResponse(); - - if (responseObject.GetType() == typeof(Error) || jsonApiContext.RequestEntity == null) - return GetErrorJson(responseObject, logger); - - return JsonApiSerializer.Serialize(responseObject, jsonApiContext); - } - - private string GetNullDataResponse() - { - return JsonConvert.SerializeObject(new Document - { - Data = null - }); - } - - private string GetErrorJson(object responseObject, ILogger logger) - { - if (responseObject.GetType() == typeof(Error)) - { - var errors = new ErrorCollection(); - errors.Add((Error)responseObject); - return errors.GetJson(); - } - else - { - logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); - return JsonConvert.SerializeObject(responseObject); - } + var writer = context.HttpContext.RequestServices.GetService(); + await writer.WriteAsync(context); } } } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs new file mode 100644 index 0000000000..65a2382a73 --- /dev/null +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Formatters +{ + public class JsonApiReader : IJsonApiReader + { + private readonly IJsonApiDeSerializer _deSerializer; + private readonly IJsonApiContext _jsonApiContext; + private readonly ILogger _logger; + + + public JsonApiReader(IJsonApiDeSerializer deSerializer, IJsonApiContext jsonApiContext, ILoggerFactory loggerFactory) + { + _deSerializer = deSerializer; + _jsonApiContext = jsonApiContext; + _logger = loggerFactory.CreateLogger(); + } + + public 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); + + try + { + var body = GetRequestBody(context.HttpContext.Request.Body); + var model = _jsonApiContext.IsRelationshipPath ? + _deSerializer.DeserializeRelationship(body) : + _deSerializer.Deserialize(body); + + if(model == null) + _logger?.LogError("An error occurred while de-serializing the payload"); + + return InputFormatterResult.SuccessAsync(model); + } + catch (JsonSerializationException ex) + { + _logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload"); + context.HttpContext.Response.StatusCode = 422; + return InputFormatterResult.FailureAsync(); + } + } + + private string GetRequestBody(Stream body) + { + using (var reader = new StreamReader(body)) + { + return reader.ReadToEnd(); + } + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs new file mode 100644 index 0000000000..8607026387 --- /dev/null +++ b/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs @@ -0,0 +1,93 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Formatters +{ + public class JsonApiWriter : IJsonApiWriter + { + private readonly ILogger _logger; + private readonly IJsonApiContext _jsonApiContext; + private readonly IJsonApiSerializer _serializer; + + public JsonApiWriter(IJsonApiContext jsonApiContext, + IJsonApiSerializer serializer, + ILoggerFactory loggerFactory) + { + _jsonApiContext = jsonApiContext; + _serializer = serializer; + _logger = loggerFactory.CreateLogger(); + } + + public async Task WriteAsync(OutputFormatterWriteContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + _logger?.LogInformation("Formatting response as JSONAPI"); + + var response = context.HttpContext.Response; + using (var writer = context.WriterFactory(response.Body, Encoding.UTF8)) + { + response.ContentType = "application/vnd.api+json"; + string responseContent; + try + { + responseContent = GetResponseBody(context.Object); + } + catch (Exception e) + { + _logger?.LogError(new EventId(), e, "An error ocurred while formatting the response"); + var errors = new ErrorCollection(); + errors.Add(new Error("400", e.Message)); + responseContent = errors.GetJson(); + response.StatusCode = 400; + } + + await writer.WriteAsync(responseContent); + await writer.FlushAsync(); + } + } + + private string GetResponseBody(object responseObject) + { + if (responseObject == null) + return GetNullDataResponse(); + + if (responseObject.GetType() == typeof(Error) || _jsonApiContext.RequestEntity == null) + return GetErrorJson(responseObject, _logger); + + return _serializer.Serialize(responseObject); + } + + private string GetNullDataResponse() + { + return JsonConvert.SerializeObject(new Document + { + Data = null + }); + } + + private string GetErrorJson(object responseObject, ILogger logger) + { + if (responseObject.GetType() == typeof(Error)) + { + var errors = new ErrorCollection(); + errors.Add((Error)responseObject); + return errors.GetJson(); + } + else + { + logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); + return JsonConvert.SerializeObject(responseObject); + } + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Internal/Generics/GenericProcessorFactory.cs b/src/JsonApiDotNetCore/Internal/Generics/GenericProcessorFactory.cs index 24a963599a..7a647bf60f 100644 --- a/src/JsonApiDotNetCore/Internal/Generics/GenericProcessorFactory.cs +++ b/src/JsonApiDotNetCore/Internal/Generics/GenericProcessorFactory.cs @@ -3,17 +3,22 @@ namespace JsonApiDotNetCore.Internal { - /// - /// Used to generate a generic operations processor when the types - /// are not know until runtime. The typical use case would be for - /// accessing relationship data. - /// - public static class GenericProcessorFactory + public class GenericProcessorFactory : IGenericProcessorFactory { - public static IGenericProcessor GetProcessor(Type type, DbContext dbContext) + private readonly DbContext _dbContext; + private readonly IServiceProvider _serviceProvider; + + public GenericProcessorFactory(DbContext dbContext, + IServiceProvider serviceProvider) + { + _dbContext = dbContext; + _serviceProvider = serviceProvider; + } + + public IGenericProcessor GetProcessor(Type type) { - var repositoryType = typeof(GenericProcessor<>).MakeGenericType(type); - return (IGenericProcessor)Activator.CreateInstance(repositoryType, dbContext); + var processorType = typeof(GenericProcessor<>).MakeGenericType(type); + return (IGenericProcessor)_serviceProvider.GetService(processorType); } } } diff --git a/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessorFactory.cs b/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessorFactory.cs new file mode 100644 index 0000000000..ce959658c4 --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessorFactory.cs @@ -0,0 +1,14 @@ +using System; + +namespace JsonApiDotNetCore.Internal +{ + /// + /// Used to generate a generic operations processor when the types + /// are not know until runtime. The typical use case would be for + /// accessing relationship data. + /// + public interface IGenericProcessorFactory + { + IGenericProcessor GetProcessor(Type type); + } +} diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 9f0d438389..a222271081 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,7 +1,7 @@  - 1.1.1 + 1.2.0 netcoreapp1.0 JsonApiDotNetCore JsonApiDotNetCore diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs new file mode 100644 index 0000000000..02f84a747a --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace JsonApiDotNetCore.Serialization +{ + public interface IJsonApiDeSerializer + { + object Deserialize(string requestBody); + object DeserializeRelationship(string requestBody); + List DeserializeList(string requestBody); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs new file mode 100644 index 0000000000..21eae09980 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs @@ -0,0 +1,7 @@ +namespace JsonApiDotNetCore.Serialization +{ + public interface IJsonApiSerializer + { + string Serialize(object entity); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 59772d9a76..63187099ea 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -8,23 +8,33 @@ using JsonApiDotNetCore.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System.Collections; -using JsonApiDotNetCore.Data; using Microsoft.EntityFrameworkCore; namespace JsonApiDotNetCore.Serialization { - public static class JsonApiDeSerializer + public class JsonApiDeSerializer : IJsonApiDeSerializer { - public static object Deserialize(string requestBody, IJsonApiContext context, - DbContext dbContext) + private readonly DbContext _dbContext; + private readonly IJsonApiContext _jsonApiContext; + private readonly IGenericProcessorFactory _genericProcessorFactor; + + public JsonApiDeSerializer(DbContext dbContext, + IJsonApiContext jsonApiContext, + IGenericProcessorFactory genericProcessorFactory) + { + _dbContext = dbContext; + _jsonApiContext = jsonApiContext; + _genericProcessorFactor = genericProcessorFactory; + } + + public object Deserialize(string requestBody) { var document = JsonConvert.DeserializeObject(requestBody); - var entity = DataToObject(document.Data, context, dbContext); + var entity = DataToObject(document.Data); return entity; } - public static object DeserializeRelationship(string requestBody, IJsonApiContext context) + public object DeserializeRelationship(string requestBody) { var data = JToken.Parse(requestBody)["data"]; @@ -35,34 +45,31 @@ public static object DeserializeRelationship(string requestBody, IJsonApiContext } - public static List DeserializeList(string requestBody, IJsonApiContext context, - DbContext dbContext) + public List DeserializeList(string requestBody) { var documents = JsonConvert.DeserializeObject(requestBody); var deserializedList = new List(); foreach (var data in documents.Data) { - var entity = DataToObject(data, context, dbContext); + var entity = DataToObject(data); deserializedList.Add((TEntity)entity); } return deserializedList; } - private static object DataToObject(DocumentData data, - IJsonApiContext context, - DbContext dbContext) + private object DataToObject(DocumentData data) { var entityTypeName = data.Type.ToProperCase(); - var contextEntity = context.ContextGraph.GetContextEntity(entityTypeName); - context.RequestEntity = contextEntity; + var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entityTypeName); + _jsonApiContext.RequestEntity = contextEntity; var entity = Activator.CreateInstance(contextEntity.EntityType); entity = _setEntityAttributes(entity, contextEntity, data.Attributes); - entity = _setRelationships(entity, contextEntity, data.Relationships, dbContext); + entity = _setRelationships(entity, contextEntity, data.Relationships); var identifiableEntity = (IIdentifiable)entity; @@ -72,7 +79,7 @@ private static object DataToObject(DocumentData data, return identifiableEntity; } - private static object _setEntityAttributes( + private object _setEntityAttributes( object entity, ContextEntity contextEntity, Dictionary attributeValues) { if (attributeValues == null || attributeValues.Count == 0) @@ -98,11 +105,10 @@ private static object _setEntityAttributes( return entity; } - private static object _setRelationships( + private object _setRelationships( object entity, ContextEntity contextEntity, - Dictionary relationships, - DbContext context) + Dictionary relationships) { if (relationships == null || relationships.Count == 0) return entity; @@ -114,13 +120,13 @@ private static object _setRelationships( if (attr.IsHasOne) entity = _setHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships); else - entity = _setHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships, context); + entity = _setHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships); } return entity; } - private static object _setHasOneRelationship(object entity, + private object _setHasOneRelationship(object entity, PropertyInfo[] entityProperties, RelationshipAttribute attr, ContextEntity contextEntity, @@ -147,12 +153,11 @@ private static object _setHasOneRelationship(object entity, return entity; } - private static object _setHasManyRelationship(object entity, + private object _setHasManyRelationship(object entity, PropertyInfo[] entityProperties, RelationshipAttribute attr, ContextEntity contextEntity, - Dictionary relationships, - DbContext context) + Dictionary relationships) { var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName); @@ -167,7 +172,7 @@ private static object _setHasManyRelationship(object entity, if (data == null) return entity; - var genericProcessor = GenericProcessorFactory.GetProcessor(attr.Type, context); + var genericProcessor = _genericProcessorFactor.GetProcessor(attr.Type); var ids = relationshipData.ManyData.Select(r => r["id"]); genericProcessor.SetRelationships(entity, attr, ids); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index 1fedd019a1..58bc9b19d0 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -1,37 +1,42 @@ using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Services; using Newtonsoft.Json; namespace JsonApiDotNetCore.Serialization { - public static class JsonApiSerializer + public class JsonApiSerializer : IJsonApiSerializer { - public static string Serialize(object entity, IJsonApiContext jsonApiContext) + private readonly IDocumentBuilder _documentBuilder; + + public JsonApiSerializer(IDocumentBuilder documentBuilder) + { + _documentBuilder = documentBuilder; + } + + public string Serialize(object entity) { if (entity is IEnumerable) - return _serializeDocuments(entity, jsonApiContext); - return _serializeDocument(entity, jsonApiContext); + return _serializeDocuments(entity); + + return _serializeDocument(entity); } - private static string _serializeDocuments(object entity, IJsonApiContext jsonApiContext) + private string _serializeDocuments(object entity) { - var documentBuilder = new DocumentBuilder(jsonApiContext); var entities = entity as IEnumerable; - var documents = documentBuilder.Build(entities); + var documents = _documentBuilder.Build(entities); return _serialize(documents); } - private static string _serializeDocument(object entity, IJsonApiContext jsonApiContext) + private string _serializeDocument(object entity) { - var documentBuilder = new DocumentBuilder(jsonApiContext); var identifiableEntity = entity as IIdentifiable; - var document = documentBuilder.Build(identifiableEntity); + var document = _documentBuilder.Build(identifiableEntity); return _serialize(document); } - private static string _serialize(object obj) + private string _serialize(object obj) { return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 1757109b71..f7d05dc4b3 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; @@ -17,6 +18,7 @@ public interface IJsonApiContext List IncludedRelationships { get; set; } bool IsRelationshipPath { get; } PageManager PageManager { get; set; } - + IMetaBuilder MetaBuilder { get; set; } + IGenericProcessorFactory GenericProcessorFactory { get; set; } } } diff --git a/src/JsonApiDotNetCore/Services/IRequestMeta.cs b/src/JsonApiDotNetCore/Services/IRequestMeta.cs new file mode 100644 index 0000000000..7dd5fdcada --- /dev/null +++ b/src/JsonApiDotNetCore/Services/IRequestMeta.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace JsonApiDotNetCore.Services +{ + public interface IRequestMeta + { + Dictionary GetMeta(); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 5ecd72872d..f9bd3f0b0f 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -14,11 +14,15 @@ public class JsonApiContext : IJsonApiContext public JsonApiContext( IContextGraph contextGraph, IHttpContextAccessor httpContextAccessor, - JsonApiOptions options) + JsonApiOptions options, + IMetaBuilder metaBuilder, + IGenericProcessorFactory genericProcessorFactory) { ContextGraph = contextGraph; _httpContextAccessor = httpContextAccessor; Options = options; + MetaBuilder = metaBuilder; + GenericProcessorFactory = genericProcessorFactory; } public JsonApiOptions Options { get; set; } @@ -30,6 +34,8 @@ public JsonApiContext( public bool IsRelationshipPath { get; private set; } public List IncludedRelationships { get; set; } public PageManager PageManager { get; set; } + public IMetaBuilder MetaBuilder { get; set; } + public IGenericProcessorFactory GenericProcessorFactory { get; set; } public IJsonApiContext ApplyContext() { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs index 7c7b145830..847d6eefff 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs @@ -57,7 +57,7 @@ public async Task Total_Record_Count_Included() // act var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(responseBody, jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(responseBody); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs new file mode 100644 index 0000000000..a1b597f7d5 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs @@ -0,0 +1,72 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Xunit; +using JsonApiDotNetCoreExample.Models; +using DotNetCoreDocs; +using JsonApiDotNetCoreExample; +using DotNetCoreDocs.Writers; +using Newtonsoft.Json; +using JsonApiDotNetCore.Models; +using System.Collections; +using System.Diagnostics; +using System.Threading; +using JsonApiDotNetCoreExampleTests.Startups; + +namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility +{ + [Collection("WebHostCollection")] + public class RequestMetaTests + { + private DocsFixture _fixture; + + public RequestMetaTests(DocsFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task Injecting_IRequestMeta_Adds_Meta_Data() + { + // arrange + var person = new Person(); + var expectedMeta = person.GetMeta(null); + var builder = new WebHostBuilder() + .UseStartup(); + + var httpMethod = new HttpMethod("GET"); + var route = $"/api/v1/people"; + + var server = new TestServer(builder); + var client = server.CreateClient(); + var request = new HttpRequestMessage(httpMethod, route); + + // act + var response = await client.SendAsync(request); + var documents = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + + // assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(documents.Meta); + Assert.NotNull(expectedMeta); + Assert.NotEmpty(expectedMeta); + + foreach(var hash in expectedMeta) + { + if(hash.Value is IList) + { + var listValue = (IList)hash.Value; + for(var i=0; i < listValue.Count; i++) + Assert.Equal(listValue[i].ToString(), ((IList)documents.Meta[hash.Key])[i].ToString()); + } + else + { + Assert.Equal(hash.Value, documents.Meta[hash.Key]); + } + } + Assert.Equal("request-meta-value", documents.Meta["request-meta"]); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 25e2e3d6fe..52b399191f 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -156,7 +156,7 @@ public async Task Can_Create_Entity_With_Client_Defined_Id_If_Configured() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -208,7 +208,7 @@ public async Task Can_Create_Guid_Identifiable_Entity_With_Client_Defined_Id_If_ // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItemCollection)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context); + var deserializedBody = (TodoItemCollection)_fixture.GetService().Deserialize(body); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -269,7 +269,7 @@ public async Task Can_Create_And_Set_HasMany_Relationships() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItemCollection)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context); + var deserializedBody = (TodoItemCollection)_fixture.GetService().Deserialize(body); var newId = deserializedBody.Id; var contextCollection = context.TodoItemCollections .Include(c => c.Owner) @@ -312,7 +312,7 @@ public async Task ShouldReceiveLocationHeader_InResponse() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs index e024786252..4fb7693d4c 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Meta.cs @@ -10,11 +10,9 @@ using Xunit; using JsonApiDotNetCore.Models; using JsonApiDotNetCoreExample.Data; -using System.Linq; using JsonApiDotNetCoreExampleTests.Startups; using JsonApiDotNetCoreExample.Models; using System.Collections; -using System; namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs index 7c1339f86c..075fc77129 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs @@ -50,7 +50,7 @@ public async Task Request_ForEmptyCollection_Returns_EmptyDataCollection() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingRelationshipsTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingRelationshipsTests.cs index dfbd862232..ffb336e2c5 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingRelationshipsTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingRelationshipsTests.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using Bogus; using DotNetCoreDocs; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs index 0e9449ad86..84e398d4b9 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs @@ -56,7 +56,7 @@ public async Task Can_Get_TodoItems() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -85,7 +85,7 @@ public async Task Can_Paginate_TodoItems() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -114,7 +114,7 @@ public async Task Can_Filter_TodoItems() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -144,7 +144,7 @@ public async Task Can_Filter_TodoItems_Using_Like_Operator() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -182,7 +182,7 @@ public async Task Can_Sort_TodoItems_By_Ordinal_Ascending() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -224,7 +224,7 @@ public async Task Can_Sort_TodoItems_By_Ordinal_Descending() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -258,7 +258,7 @@ public async Task Can_Get_TodoItem_ById() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -288,7 +288,7 @@ public async Task Can_Get_TodoItem_WithOwner() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -343,7 +343,7 @@ public async Task Can_Post_TodoItem() // Act var response = await _fixture.MakeRequest(description, request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -390,7 +390,7 @@ public async Task Can_Patch_TodoItem() // Act var response = await _fixture.MakeRequest(description, request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Services/MetaService.cs b/test/JsonApiDotNetCoreExampleTests/Services/MetaService.cs new file mode 100644 index 0000000000..91de8fda5e --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/Services/MetaService.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCoreExampleTests.Services +{ + public class MetaService : IRequestMeta + { + public Dictionary GetMeta() + { + return new Dictionary { + { "request-meta", "request-meta-value" } + }; + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/Startups/MetaStartup.cs b/test/JsonApiDotNetCoreExampleTests/Startups/MetaStartup.cs index 0c84733632..b23fded9ba 100644 --- a/test/JsonApiDotNetCoreExampleTests/Startups/MetaStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Startups/MetaStartup.cs @@ -7,6 +7,8 @@ using DotNetCoreDocs.Configuration; using System; using JsonApiDotNetCoreExample; +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExampleTests.Services; namespace JsonApiDotNetCoreExampleTests.Startups { @@ -38,6 +40,7 @@ public override IServiceProvider ConfigureServices(IServiceCollection services) }); services.AddDocumentationConfiguration(Config); + services.AddScoped(); return services.BuildServiceProvider(); } diff --git a/test/JsonApiDotNetCoreExampleTests/Unit/Builders/MetaBuilderTests.cs b/test/JsonApiDotNetCoreExampleTests/Unit/Builders/MetaBuilderTests.cs new file mode 100644 index 0000000000..5cd0b765de --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/Unit/Builders/MetaBuilderTests.cs @@ -0,0 +1,73 @@ +using Xunit; +using JsonApiDotNetCore.Builders; +using System.Collections.Generic; + +namespace JsonApiDotNetCoreExampleTests.Unit.Builders +{ + public class MetaBuilderTests + { + [Fact] + public void Can_Add_Key_Value() + { + // arrange + var builder = new MetaBuilder(); + var key = "test"; + var value = "testValue"; + + // act + builder.Add(key, value); + var result = builder.Build(); + + // assert + Assert.NotEmpty(result); + Assert.Equal(value, result[key]); + } + + [Fact] + public void Can_Add_Multiple_Values() + { + // arrange + var builder = new MetaBuilder(); + var input = new Dictionary { + { "key1", "value1" }, + { "key2", "value2" } + }; + + // act + builder.Add(input); + var result = builder.Build(); + + // assert + Assert.NotEmpty(result); + foreach (var entry in input) + Assert.Equal(input[entry.Key], result[entry.Key]); + } + + [Fact] + public void When_Adding_Duplicate_Values_Keep_Newest() + { + // arrange + var builder = new MetaBuilder(); + + var key = "key"; + var oldValue = "oldValue"; + var newValue = "newValue"; + + builder.Add(key, oldValue); + + var input = new Dictionary { + { key, newValue }, + { "key2", "value2" } + }; + + // act + builder.Add(input); + var result = builder.Build(); + + // assert + Assert.NotEmpty(result); + Assert.Equal(input.Count, result.Count); + Assert.Equal(input[key], result[key]); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/Unit/Extensions/IServiceCollectionExtensionsTests.cs b/test/JsonApiDotNetCoreExampleTests/Unit/Extensions/IServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..71a952994a --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/Unit/Extensions/IServiceCollectionExtensionsTests.cs @@ -0,0 +1,54 @@ +using Xunit; +using JsonApiDotNetCore.Builders; +using Microsoft.Extensions.DependencyInjection; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Configuration; +using Microsoft.EntityFrameworkCore; +using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Internal; +using Microsoft.AspNetCore.Http; +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Data; +using Microsoft.Extensions.Caching.Memory; +using JsonApiDotNetCoreExample.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Formatters; + +namespace JsonApiDotNetCoreExampleTests.Unit.Extensions +{ + public class IServiceCollectionExtensionsTests + { + [Fact] + public void AddJsonApiInternals_Adds_All_Required_Services() + { + // arrange + var services = new ServiceCollection(); + var jsonApiOptions = new JsonApiOptions(); + + services.AddDbContext(options => + { + options.UseMemoryCache(new MemoryCache(new MemoryCacheOptions())); + }, ServiceLifetime.Transient); + + // act + services.AddJsonApiInternals(jsonApiOptions); + var provider = services.BuildServiceProvider(); + + // assert + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService(typeof(IEntityRepository))); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService(typeof(GenericProcessor))); + } + } +}