diff --git a/Build.ps1 b/Build.ps1 index bfbd989415..8ff62d6578 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -46,18 +46,18 @@ If($env:APPVEYOR_REPO_TAG -eq $true) { IF ([string]::IsNullOrWhitespace($revision)){ Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts" - dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts + dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --include-symbols CheckLastExitCode } Else { Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision" - dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision + dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision --include-symbols CheckLastExitCode } } Else { Write-Output "VERSION-SUFFIX: alpha1-$revision" Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision" - dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision + dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision --include-symbols CheckLastExitCode } diff --git a/appveyor.yml b/appveyor.yml index ec135d19bb..78f4d3187e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -44,6 +44,7 @@ deploy: api_key: secure: 6CeYcZ4Ze+57gxfeuHzqP6ldbUkPtF6pfpVM1Gw/K2jExFrAz763gNAQ++tiacq3 skip_symbols: false + symbol_server: https://www.myget.org/F/research-institute/symbols/api/v2/package on: branch: develop - provider: NuGet diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs new file mode 100644 index 0000000000..dbd144caa4 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -0,0 +1,17 @@ +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Models; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExample.Controllers +{ + public class UsersController : JsonApiController + { + public UsersController( + IJsonApiContext jsonApiContext, + IResourceService resourceService, + ILoggerFactory loggerFactory) + : base(jsonApiContext, resourceService, loggerFactory) + { } + } +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index 4b9a40f7fd..6f50f9aa5b 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -37,7 +37,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet
Articles { get; set; } public DbSet Authors { get; set; } - public DbSet NonJsonApiResources { get; set; } + public DbSet Users { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs index cc696f54bf..ffbf105255 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Migrations/20180327120810_initial.cs @@ -1,7 +1,5 @@ -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/Models/User.cs b/src/Examples/JsonApiDotNetCoreExample/Models/User.cs new file mode 100644 index 0000000000..3b66f0dbb2 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/Models/User.cs @@ -0,0 +1,10 @@ +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCoreExample.Models +{ + public class User : Identifiable + { + [Attr("username")] public string Username { get; set; } + [Attr("password")] public string Password { get; set; } + } +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs new file mode 100644 index 0000000000..030bc4eaa4 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCoreExample.Models; + +namespace JsonApiDotNetCoreExample.Resources +{ + public class UserResource : ResourceDefinition + { + protected override List OutputAttrs() + => Remove(user => user.Password); + } +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 2c7574e1a2..ec1bdc544c 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -7,6 +7,9 @@ using Microsoft.EntityFrameworkCore; using JsonApiDotNetCore.Extensions; using System; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCoreExample.Resources; +using JsonApiDotNetCoreExample.Models; namespace JsonApiDotNetCoreExample { @@ -38,7 +41,9 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) options.Namespace = "api/v1"; options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; - }); + }) + // TODO: this should be handled via auto-discovery + .AddScoped, UserResource>(); var provider = services.BuildServiceProvider(); var appContext = provider.GetRequiredService(); diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index 523ad417bd..6f431d9291 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; diff --git a/src/Examples/ReportsExample/Services/ReportService.cs b/src/Examples/ReportsExample/Services/ReportService.cs index 7baffc6174..9e5348a612 100644 --- a/src/Examples/ReportsExample/Services/ReportService.cs +++ b/src/Examples/ReportsExample/Services/ReportService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Services; diff --git a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs index 73e355b2de..080c0a6bb7 100644 --- a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs @@ -59,7 +59,6 @@ public IContextGraph Build() _entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType)); var graph = new ContextGraph(_entities, _usesDbContext, _validationResults); - return graph; } @@ -83,7 +82,8 @@ public IContextGraphBuilder AddResource(string pluralizedTypeNam EntityType = entityType, IdentityType = idType, Attributes = GetAttributes(entityType), - Relationships = GetRelationships(entityType) + Relationships = GetRelationships(entityType), + ResourceType = GetResourceDefinitionType(entityType) }; private Link GetLinkFlags(Type entityType) @@ -104,8 +104,12 @@ protected virtual List GetAttributes(Type entityType) foreach (var prop in properties) { var attribute = (AttrAttribute)prop.GetCustomAttribute(typeof(AttrAttribute)); - if (attribute == null) continue; + if (attribute == null) + continue; + attribute.InternalAttributeName = prop.Name; + attribute.PropertyInfo = prop; + attributes.Add(attribute); } return attributes; @@ -136,6 +140,8 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope return prop.PropertyType; } + private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType); + public IContextGraphBuilder AddDbContext() where T : DbContext { _usesDbContext = true; diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 9f86a57945..c6f5f999b4 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -14,22 +15,29 @@ public class DocumentBuilder : IDocumentBuilder private readonly IContextGraph _contextGraph; private readonly IRequestMeta _requestMeta; private readonly DocumentBuilderOptions _documentBuilderOptions; + private readonly IScopedServiceProvider _scopedServiceProvider; - public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta = null, IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null) + public DocumentBuilder( + IJsonApiContext jsonApiContext, + IRequestMeta requestMeta = null, + IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, + IScopedServiceProvider scopedServiceProvider = null) { _jsonApiContext = jsonApiContext; _contextGraph = jsonApiContext.ContextGraph; _requestMeta = requestMeta; - _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); ; + _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); + _scopedServiceProvider = scopedServiceProvider; } public Document Build(IIdentifiable entity) { var contextEntity = _contextGraph.GetContextEntity(entity.GetType()); + var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition; var document = new Document { - Data = GetData(contextEntity, entity), + Data = GetData(contextEntity, entity, resourceDefinition), Meta = GetMeta(entity) }; @@ -44,8 +52,8 @@ public Document Build(IIdentifiable entity) public Documents Build(IEnumerable entities) { var entityType = entities.GetElementType(); - var contextEntity = _contextGraph.GetContextEntity(entityType); + var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition; var enumeratedEntities = entities as IList ?? entities.ToList(); var documents = new Documents @@ -59,7 +67,7 @@ public Documents Build(IEnumerable entities) foreach (var entity in enumeratedEntities) { - documents.Data.Add(GetData(contextEntity, entity)); + documents.Data.Add(GetData(contextEntity, entity, resourceDefinition)); documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity); } @@ -98,7 +106,11 @@ private List AppendIncludedObject(List includedObjec return includedObject; } + [Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")] public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity) + => GetData(contextEntity, entity, resourceDefinition: null); + + public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null) { var data = new DocumentData { @@ -111,7 +123,8 @@ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity) data.Attributes = new Dictionary(); - contextEntity.Attributes.ForEach(attr => + var resourceAttributes = resourceDefinition?.GetOutputAttrs(entity) ?? contextEntity.Attributes; + resourceAttributes.ForEach(attr => { var attributeValue = attr.GetValue(entity); if (ShouldIncludeAttribute(attr, attributeValue)) @@ -219,8 +232,9 @@ private DocumentData GetIncludedEntity(IIdentifiable entity) if (entity == null) return null; var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entity.GetType()); + var resourceDefinition = _scopedServiceProvider.GetService(contextEntity.ResourceType) as IResourceDefinition; - var data = GetData(contextEntity, entity); + var data = GetData(contextEntity, entity, resourceDefinition); data.Attributes = new Dictionary(); diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs index 4fbc8df01b..dccd6f753a 100644 --- a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; @@ -8,6 +9,9 @@ public interface IDocumentBuilder { Document Build(IIdentifiable entity); Documents Build(IEnumerable entities); + + [Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")] DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity); + DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null); } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index 20e5445ebb..82c0fe40c4 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -3,7 +3,6 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Controllers { diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index 3e78bc4f8f..5211e5fa3b 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Controllers { diff --git a/src/JsonApiDotNetCore/DependencyInjection/ServiceLocator.cs b/src/JsonApiDotNetCore/DependencyInjection/ServiceLocator.cs new file mode 100644 index 0000000000..31164ee3b9 --- /dev/null +++ b/src/JsonApiDotNetCore/DependencyInjection/ServiceLocator.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading; + +namespace JsonApiDotNetCore.DependencyInjection +{ + internal class ServiceLocator + { + public static AsyncLocal _scopedProvider = new AsyncLocal(); + public static void Initialize(IServiceProvider serviceProvider) => _scopedProvider.Value = serviceProvider; + + public static object GetService(Type type) + => _scopedProvider.Value != null + ? _scopedProvider.Value.GetService(type) + : throw new InvalidOperationException( + $"Service locator has not been initialized for the current asynchronous flow. Call {nameof(Initialize)} first." + ); + } +} diff --git a/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs b/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs index 2756524dce..9176474548 100644 --- a/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; using Microsoft.EntityFrameworkCore; diff --git a/src/JsonApiDotNetCore/Internal/ContextEntity.cs b/src/JsonApiDotNetCore/Internal/ContextEntity.cs index ff539b79ea..1e15a9c6bc 100644 --- a/src/JsonApiDotNetCore/Internal/ContextEntity.cs +++ b/src/JsonApiDotNetCore/Internal/ContextEntity.cs @@ -6,11 +6,40 @@ namespace JsonApiDotNetCore.Internal { public class ContextEntity { + /// + /// The exposed resource name + /// public string EntityName { get; set; } + + /// + /// The data model type + /// public Type EntityType { get; set; } + + /// + /// The identity member type + /// public Type IdentityType { get; set; } + + /// + /// The concrete type. + /// We store this so that we don't need to re-compute the generic type. + /// + public Type ResourceType { get; set; } + + /// + /// Exposed resource attributes + /// public List Attributes { get; set; } + + /// + /// Exposed resource relationships + /// public List Relationships { get; set; } + + /// + /// Links to include in resource responses + /// public Link Links { get; set; } = Link.All; } } diff --git a/src/JsonApiDotNetCore/Internal/ContextGraph.cs b/src/JsonApiDotNetCore/Internal/ContextGraph.cs index c27a01b7d8..9c62ae4d94 100644 --- a/src/JsonApiDotNetCore/Internal/ContextGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ContextGraph.cs @@ -17,14 +17,15 @@ public class ContextGraph : IContextGraph { internal List Entities { get; } internal List ValidationResults { get; } + internal static IContextGraph Instance { get; set; } public ContextGraph() { } - public ContextGraph(List entities, bool usesDbContext) { Entities = entities; UsesDbContext = usesDbContext; ValidationResults = new List(); + Instance = this; } // eventually, this is the planned public constructor @@ -36,6 +37,7 @@ internal ContextGraph(List entities, bool usesDbContext, Listfalse git https://github.com/json-api-dotnet/JsonApiDotNetCore + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb @@ -25,6 +28,7 @@ +