diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index a2487052d8..2dff692a31 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -58,7 +58,7 @@ public JsonApideserializer_Benchmarks() { public object DeserializeSimpleObject() => _jsonApideserializer.Deserialize(Content); private class SimpleType : Identifiable { - [Attr("name")] + [Attr] public string Name { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs index ee98b7f23d..01db4c2720 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs @@ -6,11 +6,11 @@ namespace JsonApiDotNetCoreExample.Controllers { - public class CamelCasedModelsController : JsonApiController + public class KebabCasedModelsController : JsonApiController { - public CamelCasedModelsController( + public KebabCasedModelsController( IJsonApiOptions jsonApiOptions, - IResourceService resourceService, + IResourceService resourceService, ILoggerFactory loggerFactory) : base(jsonApiOptions, resourceService, loggerFactory) { } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs index c4913689e4..19e16c26ea 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCoreExample.Controllers { - [DisableRoutingConvention, Route("custom/route/todo-items")] + [DisableRoutingConvention, Route("custom/route/todoItems")] public class TodoItemsCustomController : CustomJsonApiController { public TodoItemsCustomController( diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index cc47e88d84..de1996d5e5 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -15,4 +15,14 @@ public UsersController( : base(jsonApiOptions, resourceService, loggerFactory) { } } + + public class SuperUsersController : JsonApiController + { + public SuperUsersController( + IJsonApiOptions jsonApiOptions, + IResourceService resourceService, + ILoggerFactory loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) + { } + } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index a1887ba235..17815f9778 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -9,11 +9,12 @@ public class AppDbContext : DbContext public DbSet Passports { get; set; } public DbSet People { get; set; } public DbSet TodoItemCollections { get; set; } - public DbSet CamelCasedModels { get; set; } + public DbSet KebabCasedModels { get; set; } public DbSet
Articles { get; set; } - public DbSet Authors { get; set; } + public DbSet AuthorDifferentDbContextName { get; set; } public DbSet NonJsonApiResources { get; set; } public DbSet Users { get; set; } + public DbSet SuperUsers { get; set; } public DbSet PersonRoles { get; set; } public DbSet ArticleTags { get; set; } public DbSet IdentifiableArticleTags { get; set; } @@ -23,6 +24,8 @@ public AppDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity() .Property(t => t.CreatedDate).HasDefaultValueSql("CURRENT_TIMESTAMP").IsRequired(); @@ -43,18 +46,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasKey(bc => new { bc.ArticleId, bc.TagId }); modelBuilder.Entity() - .HasOne(t => t.StakeHolderTodo) + .HasOne(t => t.StakeHolderTodoItem) .WithMany(t => t.StakeHolders) - .HasForeignKey(t => t.StakeHolderTodoId) + .HasForeignKey(t => t.StakeHolderTodoItemId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() - .HasOne(t => t.DependentTodoItem); + .HasOne(t => t.DependentOnTodo); modelBuilder.Entity() - .HasMany(t => t.ChildrenTodoItems) - .WithOne(t => t.ParentTodoItem) - .HasForeignKey(t => t.ParentTodoItemId); + .HasMany(t => t.ChildrenTodos) + .WithOne(t => t.ParentTodo) + .HasForeignKey(t => t.ParentTodoId); modelBuilder.Entity() .HasOne(p => p.Person) @@ -63,14 +66,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.SetNull); modelBuilder.Entity() - .HasOne(p => p.ToOnePerson) - .WithOne(p => p.ToOneTodoItem) - .HasForeignKey(p => p.ToOnePersonId); + .HasOne(p => p.OneToOnePerson) + .WithOne(p => p.OneToOneTodoItem) + .HasForeignKey(p => p.OneToOnePersonId); modelBuilder.Entity() - .HasOne(p => p.ToOneTodoItem) - .WithOne(p => p.ToOnePerson) - .HasForeignKey(p => p.ToOnePersonId); + .HasOne(p => p.OneToOneTodoItem) + .WithOne(p => p.OneToOnePerson) + .HasForeignKey(p => p.OneToOnePersonId); } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs index 6b4648e8d2..d8fd68c886 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Article.cs @@ -6,10 +6,10 @@ namespace JsonApiDotNetCoreExample.Models { public class Article : Identifiable { - [Attr("name")] + [Attr] public string Name { get; set; } - [HasOne("author")] + [HasOne] public Author Author { get; set; } public int AuthorId { get; set; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs b/src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs index 8b180cc203..22a63459c7 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs @@ -15,11 +15,11 @@ public class ArticleTag public class IdentifiableArticleTag : Identifiable { public int ArticleId { get; set; } - [HasOne("article")] + [HasOne] public Article Article { get; set; } public int TagId { get; set; } - [HasOne("Tag")] + [HasOne] public Tag Tag { get; set; } public string SomeMetaData { get; set; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs index 246118a53b..fce4e7f9c3 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Author.cs @@ -5,10 +5,10 @@ namespace JsonApiDotNetCoreExample.Models { public class Author : Identifiable { - [Attr("name")] + [Attr] public string Name { get; set; } - [HasMany("articles")] + [HasMany] public List
Articles { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs b/src/Examples/JsonApiDotNetCoreExample/Models/KebabCasedModel.cs similarity index 55% rename from src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs rename to src/Examples/JsonApiDotNetCoreExample/Models/KebabCasedModel.cs index 43d5a43272..ad36d928f3 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/CamelCasedModel.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/KebabCasedModel.cs @@ -2,10 +2,9 @@ namespace JsonApiDotNetCoreExample.Models { - [Resource("camelCasedModels")] - public class CamelCasedModel : Identifiable + public class KebabCasedModel : Identifiable { - [Attr("compoundAttr")] + [Attr] public string CompoundAttr { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs index ebcfb8b09f..8775ecbab5 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs @@ -5,9 +5,9 @@ namespace JsonApiDotNetCoreExample.Models public class Passport : Identifiable { public virtual int? SocialSecurityNumber { get; set; } - public virtual bool IsLocked { get; set; } + public virtual bool IsLocked { get; set; } - [HasOne("person")] + [HasOne] public virtual Person Person { get; set; } } } \ No newline at end of file diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs index 3e18bb5feb..c5182aeb9d 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs @@ -6,7 +6,7 @@ namespace JsonApiDotNetCoreExample.Models { public class PersonRole : Identifiable { - [HasOne("person")] + [HasOne] public Person Person { get; set; } } @@ -14,43 +14,40 @@ public class Person : Identifiable, IIsLockable { public bool IsLocked { get; set; } - [Attr("first-name")] + [Attr] public string FirstName { get; set; } - [Attr("last-name")] + [Attr] public string LastName { get; set; } - [Attr("age")] + [Attr] public int Age { get; set; } - [HasMany("todo-items")] + [HasMany] public virtual List TodoItems { get; set; } - [HasMany("assigned-todo-items")] + [HasMany] public virtual List AssignedTodoItems { get; set; } - [HasMany("todo-collections")] - public virtual List TodoItemCollections { get; set; } + [HasMany] + public virtual List todoCollections { get; set; } - [HasOne("role")] + [HasOne] public virtual PersonRole Role { get; set; } public int? PersonRoleId { get; set; } - [HasOne("one-to-one-todo-item")] - public virtual TodoItem ToOneTodoItem { get; set; } + [HasOne] + public virtual TodoItem OneToOneTodoItem { get; set; } + [HasOne] + public virtual TodoItem StakeHolderTodoItem { get; set; } + public virtual int? StakeHolderTodoItemId { get; set; } - [HasOne("stake-holder-todo-item")] - public virtual TodoItem StakeHolderTodo { get; set; } - public virtual int? StakeHolderTodoId { get; set; } - - [HasOne("unincludeable-item", links: Link.All, canInclude: false)] + [HasOne(links: Link.All, canInclude: false)] public virtual TodoItem UnIncludeableItem { get; set; } - public int? PassportId { get; set; } - - [HasOne("passport")] + [HasOne] public virtual Passport Passport { get; set; } - + public int? PassportId { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs index 4576595d91..a8a88c0cda 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs @@ -13,66 +13,60 @@ public TodoItem() public bool IsLocked { get; set; } - [Attr("description")] + [Attr] public string Description { get; set; } - [Attr("ordinal")] + [Attr] public long Ordinal { get; set; } - [Attr("guid-property")] + [Attr] public Guid GuidProperty { get; set; } - [Attr("created-date")] + [Attr] public DateTime CreatedDate { get; set; } - [Attr("achieved-date", isFilterable: false, isSortable: false)] + [Attr(isFilterable: false, isSortable: false)] public DateTime? AchievedDate { get; set; } - [Attr("updated-date")] + [Attr] public DateTime? UpdatedDate { get; set; } - [Attr("calculated-value", isImmutable: true)] - public string CalculatedValue - { - get => "joe"; - } + [Attr(isImmutable: true)] + public string CalculatedValue { get => "calculated"; } - [Attr("offset-date")] + [Attr] public DateTimeOffset? OffsetDate { get; set; } public int? OwnerId { get; set; } public int? AssigneeId { get; set; } public Guid? CollectionId { get; set; } - [HasOne("owner")] + [HasOne] public virtual Person Owner { get; set; } - [HasOne("assignee")] + [HasOne] public virtual Person Assignee { get; set; } - [HasOne("one-to-one-person")] - public virtual Person ToOnePerson { get; set; } - public virtual int? ToOnePersonId { get; set; } - + [HasOne] + public virtual Person OneToOnePerson { get; set; } + public virtual int? OneToOnePersonId { get; set; } - [HasMany("stake-holders")] + [HasMany] public virtual List StakeHolders { get; set; } - [HasOne("collection")] + [HasOne] public virtual TodoItemCollection Collection { get; set; } - // cyclical to-one structure - public virtual int? DependentTodoItemId { get; set; } - [HasOne("dependent-on-todo")] - public virtual TodoItem DependentTodoItem { get; set; } - + public virtual int? DependentOnTodoId { get; set; } + [HasOne] + public virtual TodoItem DependentOnTodo { get; set; } // cyclical to-many structure - public virtual int? ParentTodoItemId {get; set;} - [HasOne("parent-todo")] - public virtual TodoItem ParentTodoItem { get; set; } - [HasMany("children-todos")] - public virtual List ChildrenTodoItems { get; set; } + public virtual int? ParentTodoId {get; set;} + [HasOne] + public virtual TodoItem ParentTodo { get; set; } + [HasMany] + public virtual List ChildrenTodos { get; set; } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs index fa17134680..2a251581d4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs @@ -4,16 +4,16 @@ namespace JsonApiDotNetCoreExample.Models { - [Resource("todo-collections")] + [Resource("todoCollections")] public class TodoItemCollection : Identifiable { - [Attr("name")] + [Attr] public string Name { get; set; } - [HasMany("todo-items")] + [HasMany] public virtual List TodoItems { get; set; } - [HasOne("owner")] + [HasOne] public virtual Person Owner { get; set; } public int? OwnerId { get; set; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/User.cs b/src/Examples/JsonApiDotNetCoreExample/Models/User.cs index f966cb84cd..d0e38b93e7 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/User.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/User.cs @@ -8,4 +8,9 @@ public class User : Identifiable [Attr] public string Username { get; set; } [Attr] public string Password { get; set; } } + + public class SuperUser : User + { + [Attr] public int SecurityLevel { get; set; } + } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs index 9aa8d8397f..aa4552cae6 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs @@ -18,7 +18,7 @@ public override QueryFilters GetQueryFilters() { return new QueryFilters { - { "first-character", (users, queryFilter) => FirstCharacterFilter(users, queryFilter) } + { "firstCharacter", (users, queryFilter) => FirstCharacterFilter(users, queryFilter) } }; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs index 32accb087a..6cc0dd1e81 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startups/MetaStartup.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using JsonApiDotNetCore.Services; using System.Collections.Generic; +using Microsoft.Extensions.Configuration; namespace JsonApiDotNetCoreExample { @@ -11,9 +12,7 @@ namespace JsonApiDotNetCoreExample /// public class MetaStartup : Startup { - public MetaStartup(IWebHostEnvironment env) - : base (env) - { } + public MetaStartup(IWebHostEnvironment env) : base(env) { } public override void ConfigureServices(IServiceCollection services) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Startups/ClientGeneratedIdsStartup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/NoDefaultPageSizeStartup.cs similarity index 82% rename from src/Examples/JsonApiDotNetCoreExample/Startups/ClientGeneratedIdsStartup.cs rename to src/Examples/JsonApiDotNetCoreExample/Startups/NoDefaultPageSizeStartup.cs index 10255d6727..489385f76d 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startups/ClientGeneratedIdsStartup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startups/NoDefaultPageSizeStartup.cs @@ -12,11 +12,9 @@ namespace JsonApiDotNetCoreExample /// This should be in JsonApiDotNetCoreExampleTests project but changes in .net core 3.0 /// do no longer allow that. See https://github.com/aspnet/AspNetCore/issues/15373. /// - public class ClientGeneratedIdsStartup : Startup + public class NoDefaultPageSizeStartup : Startup { - public ClientGeneratedIdsStartup(IWebHostEnvironment env) - : base (env) - { } + public NoDefaultPageSizeStartup(IWebHostEnvironment env) : base(env) { } public override void ConfigureServices(IServiceCollection services) { @@ -31,10 +29,8 @@ public override void ConfigureServices(IServiceCollection services) .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) .AddJsonApi(options => { options.Namespace = "api/v1"; - options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; - options.EnableResourceHooks = true; - options.LoaDatabaseValues = true; + options.LoadDatabaseValues = true; options.AllowClientGeneratedIds = true; }, discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))), diff --git a/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs index 3e9d3ca9e3..a2d11f06f5 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; using JsonApiDotNetCore.Extensions; using System; +using Microsoft.Extensions.Logging.Debug; namespace JsonApiDotNetCoreExample { @@ -36,26 +37,26 @@ public virtual void ConfigureServices(IServiceCollection services) }) .AddDbContext(options => { - options.UseNpgsql(GetDbConnectionString(), options => options.SetPostgresVersion(new Version(9,6))); + options.UseLoggerFactory(new LoggerFactory(new[] { new DebugLoggerProvider() })) + .EnableSensitiveDataLogging() + .UseNpgsql(GetDbConnectionString(), options => options.SetPostgresVersion(new Version(9,6))); }, ServiceLifetime.Transient) .AddJsonApi(options => { options.Namespace = "api/v1"; options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; - options.EnableResourceHooks = true; - options.LoaDatabaseValues = true; + options.LoadDatabaseValues = true; }, discovery => discovery.AddCurrentAssembly()); - services.AddClientSerialization(); + // once all tests have been moved to WebApplicationFactory format we can get rid of this line below + services.AddClientSerialization(); } public virtual void Configure( IApplicationBuilder app, - ILoggerFactory loggerFactory, AppDbContext context) { - context.Database.EnsureCreated(); app.UseJsonApi(); } diff --git a/src/Examples/JsonApiDotNetCoreExample/appsettings.json b/src/Examples/JsonApiDotNetCoreExample/appsettings.json old mode 100755 new mode 100644 index c468439079..38f0280d9f --- a/src/Examples/JsonApiDotNetCoreExample/appsettings.json +++ b/src/Examples/JsonApiDotNetCoreExample/appsettings.json @@ -7,7 +7,10 @@ "LogLevel": { "Default": "Warning", "System": "Warning", - "Microsoft": "Warning" + "Microsoft": "Warning", + "LogLevel": { + "Microsoft.EntityFrameworkCore": "Debug" + } } } } diff --git a/src/Examples/NoEntityFrameworkExample/.gitignore b/src/Examples/NoEntityFrameworkExample/.gitignore index 0ca27f04e1..700191e656 100644 --- a/src/Examples/NoEntityFrameworkExample/.gitignore +++ b/src/Examples/NoEntityFrameworkExample/.gitignore @@ -22,6 +22,8 @@ bld/ [Bb]in/ [Oo]bj/ +Properties/launchSettings.json + # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot diff --git a/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs b/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs index b1021d18f5..d380b0d018 100644 --- a/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs +++ b/src/Examples/NoEntityFrameworkExample/Models/TodoItem.cs @@ -12,25 +12,25 @@ public TodoItem() public bool IsLocked { get; set; } - [Attr("description")] + [Attr] public string Description { get; set; } - [Attr("ordinal")] + [Attr] public long Ordinal { get; set; } - [Attr("guid-property")] + [Attr] public Guid GuidProperty { get; set; } - [Attr("created-date")] + [Attr] public DateTime CreatedDate { get; set; } - [Attr("achieved-date", isFilterable: false, isSortable: false)] + [Attr(isFilterable: false, isSortable: false)] public DateTime? AchievedDate { get; set; } - [Attr("updated-date")] + [Attr] public DateTime? UpdatedDate { get; set; } - [Attr("offset-date")] + [Attr] public DateTimeOffset? OffsetDate { get; set; } } } diff --git a/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json b/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json deleted file mode 100644 index 1dff6cfe69..0000000000 --- a/src/Examples/NoEntityFrameworkExample/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:57181/", - "sslPort": 0 - } - }, - "profiles": { - "NoEntityFrameworkExample": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:5000/" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/src/Examples/NoEntityFrameworkExample/Startup.cs b/src/Examples/NoEntityFrameworkExample/Startup.cs index 1d6aef07c5..f21f59c7ba 100644 --- a/src/Examples/NoEntityFrameworkExample/Startup.cs +++ b/src/Examples/NoEntityFrameworkExample/Startup.cs @@ -37,7 +37,7 @@ public virtual void ConfigureServices(IServiceCollection services) builder.AddConsole(); }).AddJsonApi( options => options.Namespace = "api/v1", - resources: resources => resources.AddResource("todo-items"), + resources: resources => resources.AddResource("todoItems"), mvcBuilder: mvcBuilder ); services.AddScoped, TodoItemService>(); diff --git a/src/Examples/ReportsExample/Models/Report.cs b/src/Examples/ReportsExample/Models/Report.cs index 39c07aca3d..241221cdaa 100644 --- a/src/Examples/ReportsExample/Models/Report.cs +++ b/src/Examples/ReportsExample/Models/Report.cs @@ -2,10 +2,10 @@ public class Report : Identifiable { - [Attr("title")] + [Attr] public string Title { get; set; } - [Attr("complex-type")] + [Attr] public ComplexType ComplexType { get; set; } } diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs index 480f2a0f62..bafa6f7689 100644 --- a/src/Examples/ReportsExample/Startup.cs +++ b/src/Examples/ReportsExample/Startup.cs @@ -1,9 +1,7 @@ using JsonApiDotNetCore.Extensions; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace ReportsExample { diff --git a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs index a9a74cf9a7..3b38531918 100644 --- a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; @@ -13,7 +14,6 @@ public interface IResourceGraphBuilder /// Construct the /// IResourceGraph Build(); - /// /// Add a json:api resource /// @@ -24,8 +24,6 @@ public interface IResourceGraphBuilder /// See . /// IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - - /// /// Add a json:api resource /// @@ -37,7 +35,6 @@ public interface IResourceGraphBuilder /// See . /// IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - /// /// Add a Json:Api resource /// @@ -49,12 +46,5 @@ public interface IResourceGraphBuilder /// See . /// IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null); - - /// - /// Add all the models that are part of the provided - /// that also implement - /// - /// The implementation type. - IResourceGraphBuilder AddDbContext() where T : DbContext; } } diff --git a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs index 302aeb831f..d7b070f33a 100644 --- a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs @@ -20,6 +20,7 @@ using JsonApiDotNetCore.Serialization.Server.Builders; using JsonApiDotNetCore.Serialization.Server; using Microsoft.Extensions.DependencyInjection.Extensions; +using JsonApiDotNetCore.Extensions; namespace JsonApiDotNetCore.Builders { @@ -30,10 +31,10 @@ namespace JsonApiDotNetCore.Builders public class JsonApiApplicationBuilder { public readonly JsonApiOptions JsonApiOptions = new JsonApiOptions(); - private IResourceGraphBuilder _resourceGraphBuilder; + internal IResourceGraphBuilder _resourceGraphBuilder; + internal bool _usesDbContext; + internal readonly IServiceCollection _services; private IServiceDiscoveryFacade _serviceDiscoveryFacade; - private bool _usesDbContext; - private readonly IServiceCollection _services; private readonly IMvcCoreBuilder _mvcBuilder; public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder) @@ -42,11 +43,6 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv _mvcBuilder = mvcBuilder; } - internal void ConfigureLogging() - { - _services.AddLogging(); - } - /// /// Executes the action provided by the user to configure /// @@ -82,7 +78,7 @@ public void ConfigureMvc() } /// - /// Executes autodiscovery of JADNC services. + /// Executes auto-discovery of JADNC services. /// public void AutoDiscover(Action autoDiscover) { @@ -98,18 +94,6 @@ public void ConfigureResources(Action resourceGraphBuilde resourceGraphBuilder(_resourceGraphBuilder); } - /// - /// Executes the action provided by the user to configure the resources using . - /// Additionally, inspects the EF core database context for models that implement IIdentifiable. - /// - public void ConfigureResources(Action resourceGraphBuilder) where TContext : DbContext - { - _resourceGraphBuilder.AddDbContext(); - _usesDbContext = true; - _services.AddScoped>(); - resourceGraphBuilder?.Invoke(_resourceGraphBuilder); - } - /// /// Registers the remaining internals. /// @@ -150,6 +134,9 @@ public void ConfigureServices() _services.AddScoped(typeof(IResourceService<>), typeof(DefaultResourceService<>)); _services.AddScoped(typeof(IResourceService<,>), typeof(DefaultResourceService<,>)); + _services.AddScoped(typeof(IResourceQueryService<,>), typeof(DefaultResourceService<,>)); + _services.AddScoped(typeof(IResourceCmdService<,>), typeof(DefaultResourceService<,>)); + _services.AddSingleton(JsonApiOptions); _services.AddSingleton(resourceGraph); _services.AddSingleton(); @@ -219,7 +206,7 @@ private void AddServerSerialization() private void RegisterJsonApiStartupServices() { _services.AddSingleton(JsonApiOptions); - _services.TryAddSingleton(new KebabCaseFormatter()); + _services.TryAddSingleton(new CamelCaseFormatter()); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(sp => new ServiceDiscoveryFacade(_services, sp.GetRequiredService())); diff --git a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 405fe64936..8b0ad0e348 100644 --- a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -10,29 +10,28 @@ using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Links; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Builders { public class ResourceGraphBuilder : IResourceGraphBuilder { - private readonly List _entities = new List(); - private readonly List _validationResults = new List(); - private readonly IResourceNameFormatter _resourceNameFormatter = new KebabCaseFormatter(); + private List _resources { get; set; } = new List(); + private List _validationResults { get; set; } = new List(); + private IResourceNameFormatter _formatter { get; set; } = new CamelCaseFormatter(); public ResourceGraphBuilder() { } public ResourceGraphBuilder(IResourceNameFormatter formatter) { - _resourceNameFormatter = formatter; + _formatter = formatter; } /// public IResourceGraph Build() { - _entities.ForEach(SetResourceLinksOptions); - var resourceGraph = new ResourceGraph(_entities, _validationResults); + _resources.ForEach(SetResourceLinksOptions); + var resourceGraph = new ResourceGraph(_resources, _validationResults); return resourceGraph; } @@ -56,13 +55,19 @@ public IResourceGraphBuilder AddResource(string pluralizedTypeNa => AddResource(typeof(TResource), typeof(TId), pluralizedTypeName); /// - public IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null) + public IResourceGraphBuilder AddResource(Type resourceType, Type idType = null, string pluralizedTypeName = null) { - AssertEntityIsNotAlreadyDefined(entityType); - - pluralizedTypeName = pluralizedTypeName ?? _resourceNameFormatter.FormatResourceName(entityType); - - _entities.Add(GetEntity(pluralizedTypeName, entityType, idType)); + AssertEntityIsNotAlreadyDefined(resourceType); + if (resourceType.Implements()) + { + pluralizedTypeName ??= _formatter.FormatResourceName(resourceType); + idType ??= TypeLocator.GetIdType(resourceType); + _resources.Add(GetEntity(pluralizedTypeName, resourceType, idType)); + } + else + { + _validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. ")); + } return this; } @@ -93,7 +98,7 @@ protected virtual List GetAttributes(Type entityType) { var idAttr = new AttrAttribute() { - PublicAttributeName = _resourceNameFormatter.FormatPropertyName(prop), + PublicAttributeName = _formatter.FormatPropertyName(prop), PropertyInfo = prop, InternalAttributeName = prop.Name }; @@ -105,7 +110,7 @@ protected virtual List GetAttributes(Type entityType) if (attribute == null) continue; - attribute.PublicAttributeName = attribute.PublicAttributeName ?? _resourceNameFormatter.FormatPropertyName(prop); + attribute.PublicAttributeName = attribute.PublicAttributeName ?? _formatter.FormatPropertyName(prop); attribute.InternalAttributeName = prop.Name; attribute.PropertyInfo = prop; @@ -123,7 +128,7 @@ protected virtual List GetRelationships(Type entityType) var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute)); if (attribute == null) continue; - attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _resourceNameFormatter.FormatPropertyName(prop); + attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _formatter.FormatPropertyName(prop); attribute.InternalRelationshipName = prop.Name; attribute.RightType = GetRelationshipType(attribute, prop); attribute.LeftType = entityType; @@ -178,63 +183,9 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType); - /// - public IResourceGraphBuilder AddDbContext() where T : DbContext - { - var contextType = typeof(T); - var contextProperties = contextType.GetProperties(); - foreach (var property in contextProperties) - { - var dbSetType = property.PropertyType; - if (dbSetType.GetTypeInfo().IsGenericType - && dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>)) - { - var entityType = dbSetType.GetGenericArguments()[0]; - AssertEntityIsNotAlreadyDefined(entityType); - var (isJsonApiResource, idType) = GetIdType(entityType); - if (isJsonApiResource) - _entities.Add(GetEntity(GetResourceNameFromDbSetProperty(property, entityType), entityType, idType)); - } - } - - return this; - } - - private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType) - { - // this check is actually duplicated in the DefaultResourceNameFormatter - // however, we perform it here so that we allow class attributes to be prioritized over - // the DbSet attribute. Eventually, the DbSet attribute should be deprecated. - // - // check the class definition first - // [Resource("models"] public class Model : Identifiable { /* ... */ } - if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute) - return classResourceAttribute.ResourceName; - - // check the DbContext member next - // [Resource("models")] public DbSet Models { get; set; } - if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute) - return resourceAttribute.ResourceName; - - // fallback to the established convention using the DbSet Property.Name - // e.g DbSet FooBars { get; set; } => "foo-bars" - return _resourceNameFormatter.ApplyCasingConvention(property.Name); - } - - private (bool isJsonApiResource, Type idType) GetIdType(Type resourceType) - { - var possible = TypeLocator.GetIdType(resourceType); - if (possible.isJsonApiResource) - return possible; - - _validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. ")); - - return (false, null); - } - private void AssertEntityIsNotAlreadyDefined(Type entityType) { - if (_entities.Any(e => e.ResourceType == entityType)) + if (_resources.Any(e => e.ResourceType == entityType)) throw new InvalidOperationException($"Cannot add entity type {entityType} to context resourceGraph, there is already an entity of that type configured."); } } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index a969a4dbf0..374441474d 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -8,7 +8,7 @@ public interface IJsonApiOptions : ILinksConfiguration, ISerializerOptions /// /// Defaults to . /// - bool LoaDatabaseValues { get; set; } + bool LoadDatabaseValues { get; set; } /// /// Whether or not the total-record count should be included in all document /// level meta objects. diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index fe41af6602..8530cd8fa2 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -42,9 +42,9 @@ public class JsonApiOptions : IJsonApiOptions /// /// Whether or not ResourceHooks are enabled. /// - /// Default is set to for backward compatibility. + /// Default is set to /// - public bool EnableResourceHooks { get; set; } = false; + public bool EnableResourceHooks { get; set; } = true; /// /// Whether or not database values should be included by default @@ -52,7 +52,7 @@ public class JsonApiOptions : IJsonApiOptions /// /// Defaults to . /// - public bool LoaDatabaseValues { get; set; } = false; + public bool LoadDatabaseValues { get; set; } = false; /// /// The base URL Namespace diff --git a/src/JsonApiDotNetCore/Data/IResourceReadRepository.cs b/src/JsonApiDotNetCore/Data/IResourceReadRepository.cs index d2d8946d2d..63867c6bea 100644 --- a/src/JsonApiDotNetCore/Data/IResourceReadRepository.cs +++ b/src/JsonApiDotNetCore/Data/IResourceReadRepository.cs @@ -32,7 +32,7 @@ public interface IResourceReadRepository /// /// /// - /// _todoItemsRepository.GetAndIncludeAsync(1, "achieved-date"); + /// _todoItemsRepository.GetAndIncludeAsync(1, "achievedDate"); /// /// IQueryable Include(IQueryable entities, IEnumerable inclusionChain); diff --git a/src/JsonApiDotNetCore/Extensions/EntityFrameworkCoreExtension.cs b/src/JsonApiDotNetCore/Extensions/EntityFrameworkCoreExtension.cs new file mode 100644 index 0000000000..af9272f48c --- /dev/null +++ b/src/JsonApiDotNetCore/Extensions/EntityFrameworkCoreExtension.cs @@ -0,0 +1,111 @@ +using System; +using System.Reflection; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Graph; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Data; + +namespace JsonApiDotNetCore.Extensions.EntityFrameworkCore +{ + + /// + /// Extensions for configuring JsonApiDotNetCore with EF Core + /// + public static class IResourceGraphBuilderExtensions + { + /// + /// Add all the models that are part of the provided + /// that also implement + /// + /// The implementation type. + public static IResourceGraphBuilder AddDbContext(this IResourceGraphBuilder resourceGraphBuilder) where TDbContext : DbContext + { + var builder = (ResourceGraphBuilder)resourceGraphBuilder; + var contextType = typeof(TDbContext); + var contextProperties = contextType.GetProperties(); + foreach (var property in contextProperties) + { + var dbSetType = property.PropertyType; + if (dbSetType.GetTypeInfo().IsGenericType + && dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>)) + { + var resourceType = dbSetType.GetGenericArguments()[0]; + builder.AddResource(resourceType, pluralizedTypeName: GetResourceNameFromDbSetProperty(property, resourceType)); + } + } + return resourceGraphBuilder; + } + + private static string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType) + { + // this check is actually duplicated in the DefaultResourceNameFormatter + // however, we perform it here so that we allow class attributes to be prioritized over + // the DbSet attribute. Eventually, the DbSet attribute should be deprecated. + // + // check the class definition first + // [Resource("models"] public class Model : Identifiable { /* ... */ } + if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute) + return classResourceAttribute.ResourceName; + + // check the DbContext member next + // [Resource("models")] public DbSet Models { get; set; } + if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute) + return resourceAttribute.ResourceName; + + return null; + } + } + + /// + /// Extensions for configuring JsonApiDotNetCore with EF Core + /// + public static class IServiceCollectionExtensions + { + /// + /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. + /// + /// + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action options = null, + Action discovery = null, + Action resources = null, + IMvcCoreBuilder mvcBuilder = null) + where TDbContext : DbContext + { + var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); + if (options != null) + application.ConfigureJsonApiOptions(options); + application.ConfigureMvc(); + if (discovery != null) + application.AutoDiscover(discovery); + application.ConfigureResources(resources); + application.ConfigureServices(); + return services; + } + } + + /// + /// Extensions for configuring JsonApiDotNetCore with EF Core + /// + public static class JsonApiApplicationBuildExtensions + { + /// + /// Executes the action provided by the user to configure the resources using . + /// Additionally, inspects the EF core database context for models that implement IIdentifiable. + /// + public static void ConfigureResources(this JsonApiApplicationBuilder builder, Action resourceGraphBuilder) where TContext : DbContext + { + builder._resourceGraphBuilder.AddDbContext(); + builder._usesDbContext = true; + builder._services.AddScoped>(); + resourceGraphBuilder?.Invoke(builder._resourceGraphBuilder); + } + } +} diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index aab18dec6b..eb30283428 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -18,33 +18,9 @@ namespace JsonApiDotNetCore.Extensions // ReSharper disable once InconsistentNaming public static class IServiceCollectionExtensions { - /// - /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddJsonApi(this IServiceCollection services, - Action options = null, - Action resources = null, - IMvcCoreBuilder mvcBuilder = null) - where TEfCoreDbContext : DbContext - { - var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); - if (options != null) - application.ConfigureJsonApiOptions(options); - application.ConfigureLogging(); - application.ConfigureMvc(); - application.ConfigureResources(resources); - application.ConfigureServices(); - return services; - } - /// /// Enabling JsonApiDotNetCore using manual declaration to build the ResourceGraph. - /// z + /// /// /// /// diff --git a/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/CamelCaseFormatter.cs b/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/CamelCaseFormatter.cs index 7e9a4d1e27..17a94cdcde 100644 --- a/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/CamelCaseFormatter.cs +++ b/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/CamelCaseFormatter.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCore.Graph { /// - /// Uses kebab-case as formatting options in the route and request/response body. + /// Uses camelCase as formatting options in the route and request/response body. /// /// /// diff --git a/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/KebabCaseFormatter.cs b/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/KebabCaseFormatter.cs index 22144a4769..42a48a8572 100644 --- a/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/KebabCaseFormatter.cs +++ b/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/KebabCaseFormatter.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Graph /// /// /// _default.FormatResourceName(typeof(TodoItem)).Dump(); - /// // > "todo-items" + /// // > "todoItems" /// /// /// @@ -25,7 +25,7 @@ namespace JsonApiDotNetCore.Graph /// /// /// _default.ApplyCasingConvention("TodoItems"); - /// // > "todo-items" + /// // > "todoItems" /// /// _default.ApplyCasingConvention("TodoItem"); /// // > "todo-item" diff --git a/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/ResourceNameFormatterBase.cs b/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/ResourceNameFormatterBase.cs index 319824041d..6c6fc86f36 100644 --- a/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/ResourceNameFormatterBase.cs +++ b/src/JsonApiDotNetCore/Graph/ResourceNameFormatters/ResourceNameFormatterBase.cs @@ -35,7 +35,7 @@ public string FormatResourceName(Type type) /// /// Uses the internal PropertyInfo to determine the external resource name. - /// By default the name will be formatted to kebab-case. + /// By default the name will be formatted to camelCase. /// public string FormatPropertyName(PropertyInfo property) => ApplyCasingConvention(property.Name); } diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index 074914faa3..ac5cdd79fa 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -140,13 +140,9 @@ private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor id private void AddResourceToGraph(ResourceDescriptor identifiable) { - var resourceName = FormatResourceName(identifiable.ResourceType); - _resourceGraphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType, resourceName); + _resourceGraphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType); } - private string FormatResourceName(Type resourceType) - => new KebabCaseFormatter().FormatResourceName(resourceType); - /// /// Add implementations to container. /// diff --git a/src/JsonApiDotNetCore/Graph/TypeLocator.cs b/src/JsonApiDotNetCore/Graph/TypeLocator.cs index 1e82e438c3..de5a2cd82f 100644 --- a/src/JsonApiDotNetCore/Graph/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Graph/TypeLocator.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Models; using System; using System.Collections.Generic; @@ -19,30 +20,10 @@ static class TypeLocator /// Determine whether or not this is a json:api resource by checking if it implements . /// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)` /// - public static (bool isJsonApiResource, Type idType) GetIdType(Type resourceType) + public static Type GetIdType(Type resourceType) { - var identitifableType = GetIdentifiableIdType(resourceType); - return (identitifableType != null) - ? (true, identitifableType) - : (false, null); - } - - private static Type GetIdentifiableIdType(Type identifiableType) - => GetIdentifiableInterface(identifiableType)?.GetGenericArguments()[0]; - - private static Type GetIdentifiableInterface(Type type) - => type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>)); - - // TODO: determine if this optimization is even helpful... - private static Type[] GetAssemblyTypes(Assembly assembly) - { - if (_typeCache.TryGetValue(assembly, out var types) == false) - { - types = assembly.GetTypes(); - _typeCache[assembly] = types; - } - - return types; + var identifiableInterface = resourceType.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>)); + return identifiableInterface?.GetGenericArguments()[0]; } /// @@ -76,13 +57,11 @@ private static IEnumerable FindIdentifableTypes(Assembly ass /// internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor descriptor) { - var possible = GetIdType(type); - if (possible.isJsonApiResource) + if (type.Implements()) { - descriptor = new ResourceDescriptor(type, possible.idType); + descriptor = new ResourceDescriptor(type, GetIdType(type)); return true; } - descriptor = ResourceDescriptor.Empty; return false; } diff --git a/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs b/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs index 9b95e24562..bfade1c50d 100644 --- a/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs +++ b/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; +using Microsoft.Extensions.DependencyInjection; namespace JsonApiDotNetCore.Hooks { @@ -12,6 +12,7 @@ namespace JsonApiDotNetCore.Hooks /// public class HooksDiscovery : IHooksDiscovery where TResource : class, IIdentifiable { + private readonly Type _boundResourceDefinitionType = typeof(ResourceDefinition); private readonly ResourceHook[] _allHooks; private readonly ResourceHook[] _databaseValuesAttributeAllowed = { @@ -19,71 +20,66 @@ public class HooksDiscovery : IHooksDiscovery where TResou ResourceHook.BeforeUpdateRelationship, ResourceHook.BeforeDelete }; + /// public ResourceHook[] ImplementedHooks { get; private set; } public ResourceHook[] DatabaseValuesEnabledHooks { get; private set; } public ResourceHook[] DatabaseValuesDisabledHooks { get; private set; } - - public HooksDiscovery() + public HooksDiscovery(IServiceProvider provider) { _allHooks = Enum.GetValues(typeof(ResourceHook)) .Cast() .Where(h => h != ResourceHook.None) .ToArray(); - DiscoverImplementedHooksForModel(); + + Type containerType; + using (var scope = provider.CreateScope()) + { + containerType = scope.ServiceProvider.GetService(_boundResourceDefinitionType)?.GetType(); + } + + DiscoverImplementedHooks(containerType); } /// /// Discovers the implemented hooks for a model. /// /// The implemented hooks for model. - void DiscoverImplementedHooksForModel() + void DiscoverImplementedHooks(Type containerType) { - Type parameterizedResourceDefinition = typeof(ResourceDefinition); - var derivedTypes = TypeLocator.GetDerivedTypes(typeof(TResource).Assembly, parameterizedResourceDefinition).ToList(); - - - var implementedHooks = new List(); - var enabledHooks = new List() { ResourceHook.BeforeImplicitUpdateRelationship } ; - var disabledHooks = new List(); - Type targetType = null; - try + if (containerType == null || containerType == _boundResourceDefinitionType) { - targetType = derivedTypes.SingleOrDefault(); // multiple containers is not supported + return; } - catch - { - throw new JsonApiSetupException($"It is currently not supported to" + - "implement hooks across multiple implementations of ResourceDefinition"); - } - if (targetType != null) + + var implementedHooks = new List(); + // this hook can only be used with enabled database values + var databaseValuesEnabledHooks = new List { ResourceHook.BeforeImplicitUpdateRelationship }; + var databaseValuesDisabledHooks = new List(); + foreach (var hook in _allHooks) { - foreach (var hook in _allHooks) + var method = containerType.GetMethod(hook.ToString("G")); + if (method.DeclaringType == _boundResourceDefinitionType) + continue; + + implementedHooks.Add(hook); + var attr = method.GetCustomAttributes(true).OfType().SingleOrDefault(); + if (attr != null) { - var method = targetType.GetMethod(hook.ToString("G")); - if (method.DeclaringType != parameterizedResourceDefinition) + if (!_databaseValuesAttributeAllowed.Contains(hook)) { - implementedHooks.Add(hook); - var attr = method.GetCustomAttributes(true).OfType().SingleOrDefault(); - if (attr != null) - { - if (!_databaseValuesAttributeAllowed.Contains(hook)) - { - throw new JsonApiSetupException($"DatabaseValuesAttribute cannot be used on hook" + - $"{hook.ToString("G")} in resource definition {parameterizedResourceDefinition.Name}"); - } - var targetList = attr.value ? enabledHooks : disabledHooks; - targetList.Add(hook); - } - } + throw new JsonApiSetupException($"DatabaseValuesAttribute cannot be used on hook" + + $"{hook.ToString("G")} in resource definition {containerType.Name}"); + } + var targetList = attr.value ? databaseValuesEnabledHooks : databaseValuesDisabledHooks; + targetList.Add(hook); } - } - ImplementedHooks = implementedHooks.ToArray(); - DatabaseValuesDisabledHooks = disabledHooks.ToArray(); - DatabaseValuesEnabledHooks = enabledHooks.ToArray(); + ImplementedHooks = implementedHooks.ToArray(); + DatabaseValuesDisabledHooks = databaseValuesDisabledHooks.ToArray(); + DatabaseValuesEnabledHooks = databaseValuesEnabledHooks.ToArray(); } } -} +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs b/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs index 1477cc0ec1..6a47e9d2a0 100644 --- a/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs +++ b/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs @@ -1,10 +1,10 @@ using System; namespace JsonApiDotNetCore.Hooks { - public class LoaDatabaseValues : Attribute + public class LoadDatabaseValues : Attribute { public readonly bool value; - public LoaDatabaseValues(bool mode = true) + public LoadDatabaseValues(bool mode = true) { value = mode; } diff --git a/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs b/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs index c43ae530c4..e0de8a11ff 100644 --- a/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs @@ -90,7 +90,7 @@ public IEnumerable> GetDiffs() private HashSet ThrowNoDbValuesError() { - throw new MemberAccessException("Cannot iterate over the diffs if the LoaDatabaseValues option is set to false"); + throw new MemberAccessException($"Cannot iterate over the diffs if the ${nameof(LoadDatabaseValues)} option is set to false"); } } diff --git a/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs index 5e2ee7731d..af364d4ceb 100644 --- a/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs @@ -102,7 +102,7 @@ public bool ShouldLoadDbValues(Type entityType, ResourceHook hook) return false; if (discovery.DatabaseValuesEnabledHooks.Contains(hook)) return true; - return _options.LoaDatabaseValues; + return _options.LoadDatabaseValues; } bool ShouldExecuteHook(RightType entityType, ResourceHook hook) diff --git a/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs b/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs index eb68142e31..ca84b60a47 100644 --- a/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs @@ -15,23 +15,23 @@ namespace JsonApiDotNetCore.Internal /// /// The default routing convention registers the name of the resource as the route /// using the that is registered. The default for this is - /// a kebab-case formatter. If the controller directly inherits from JsonApiMixin and there is no + /// a camelCase formatter. If the controller directly inherits from JsonApiMixin and there is no /// resource directly associated, it used the name of the controller instead of the name of the type. /// /// /// public class SomeResourceController: JsonApiController{SomeResource} { } - /// // => /some-resources/relationship/related-resource + /// // => /someResources/relationship/relatedResource /// /// public class RandomNameController{SomeResource} : JsonApiController{SomeResource} { } - /// // => /some-resources/relationship/related-resource + /// // => /someResources/relationship/relatedResource /// - /// // when using the camelCase formatter: + /// // when using the kebab-case formatter: /// public class SomeResourceController{SomeResource} : JsonApiController{SomeResource} { } - /// // => /someResources/relationship/relatedResource + /// // => /some-resources/relationship/related-resource /// - /// // when inheriting from JsonApiMixin formatter: + /// // when inheriting from JsonApiMixin controller: /// public class SomeVeryCustomController{SomeResource} : JsonApiMixin { } - /// // => /some-very-customs/relationship/related-resource + /// // => /someVeryCustoms/relationship/relatedResource /// public class DefaultRoutingConvention : IJsonApiRoutingConvention, IControllerResourceMapping { @@ -58,6 +58,7 @@ public void Apply(ApplicationModel application) foreach (var controller in application.Controllers) { var resourceType = GetResourceTypeFromController(controller.ControllerType); + if (resourceType != null) _registeredResources.Add(controller.ControllerName, resourceType); diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index b747656b40..7fadf92c6f 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -27,7 +27,6 @@ -