Skip to content

Dynamic controller generation #900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

namespace JsonApiDotNetCoreExample.Definitions
{
public class ArticleHooksDefinition : ResourceHooksDefinition<Article>
public class ArticleHooks : ResourceHooksDefinition<Article>
{
public ArticleHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
public ArticleHooks(IResourceGraph resourceGraph) : base(resourceGraph) { }

public override IEnumerable<Article> OnReturn(HashSet<Article> resources, ResourcePipeline pipeline)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

namespace JsonApiDotNetCoreExample.Definitions
{
public class PassportHooksDefinition : ResourceHooksDefinition<Passport>
public class PassportHooks : ResourceHooksDefinition<Passport>
{
public PassportHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
public PassportHooks(IResourceGraph resourceGraph) : base(resourceGraph)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

namespace JsonApiDotNetCoreExample.Definitions
{
public class PersonHooksDefinition : LockableHooksDefinition<Person>
public class PersonHooks : LockableHooksDefinition<Person>
{
public PersonHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
public PersonHooks(IResourceGraph resourceGraph) : base(resourceGraph) { }

public override IEnumerable<string> BeforeUpdateRelationship(HashSet<string> ids, IRelationshipsDictionary<Person> resourcesByRelationship, ResourcePipeline pipeline)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

namespace JsonApiDotNetCoreExample.Definitions
{
public class TagHooksDefinition : ResourceHooksDefinition<Tag>
public class TagHooks : ResourceHooksDefinition<Tag>
{
public TagHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
public TagHooks(IResourceGraph resourceGraph) : base(resourceGraph) { }

public override IEnumerable<Tag> BeforeCreate(IResourceHashSet<Tag> affected, ResourcePipeline pipeline)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

namespace JsonApiDotNetCoreExample.Definitions
{
public class TodoHooksDefinition : LockableHooksDefinition<TodoItem>
public class TodoItemHooks : LockableHooksDefinition<TodoItem>
{
public TodoHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { }
public TodoItemHooks(IResourceGraph resourceGraph) : base(resourceGraph) { }

public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null)
{
Expand Down
12 changes: 12 additions & 0 deletions src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;

namespace JsonApiDotNetCore.Configuration
{
/// <summary>
/// Service that allows customization of the dynamic controller generation. Register a
/// custom implementation before calling <see cref="ServiceCollectionExtensions.AddJsonApi"/>
/// in order to override the default controller generation behaviour.
/// </summary>
public interface IJsonApiControllerGenerator : IApplicationFeatureProvider<ControllerFeature> { }
}
6 changes: 6 additions & 0 deletions src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ public interface IJsonApiOptions
/// </summary>
JsonSerializerSettings SerializerSettings { get; }

/// <summary>
/// Determines whether JsonApiDotNetCore will dynamically register controllers for every registered resource
/// in the resource graph. Defaults to true.
/// </summary>
bool AutoGenerateControllers { get; }

internal DefaultContractResolver SerializerContractResolver => (DefaultContractResolver) SerializerSettings.ContractResolver;
}
}
30 changes: 27 additions & 3 deletions src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, ID
private readonly ResourceGraphBuilder _resourceGraphBuilder;
private readonly ServiceDiscoveryFacade _serviceDiscoveryFacade;
private readonly ServiceProvider _intermediateProvider;

private IResourceGraph _resourceGraph;

public Action<MvcOptions> ConfigureMvcOptions { get; set; }

public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder)
Expand Down Expand Up @@ -83,8 +84,8 @@ public void AddResourceGraph(ICollection<Type> dbContextTypes, Action<ResourceGr

configureResourceGraph?.Invoke(_resourceGraphBuilder);

var resourceGraph = _resourceGraphBuilder.Build();
_services.AddSingleton(resourceGraph);
_resourceGraph = _resourceGraphBuilder.Build();
_services.AddSingleton(_resourceGraph);
}

/// <summary>
Expand All @@ -101,13 +102,36 @@ public void ConfigureMvc()
ConfigureMvcOptions?.Invoke(options);
});

if (_options.AutoGenerateControllers)
{
RegisterControllerFeatureProvider();
}

if (_options.ValidateModelState)
{
_mvcBuilder.AddDataAnnotations();
_services.AddSingleton<IModelMetadataProvider, JsonApiModelMetadataProvider>();
}
}

private void RegisterControllerFeatureProvider()
{
IJsonApiControllerGenerator controllerFeatureProvider;
if (_services.Any(descriptor => descriptor.ServiceType == typeof(IJsonApiControllerGenerator)))
{
using (var provider = _services.BuildServiceProvider())
{
controllerFeatureProvider = provider.GetRequiredService<IJsonApiControllerGenerator>();
}
}
else
{
controllerFeatureProvider = new JsonApiControllerGenerator(_resourceGraph);
}

_mvcBuilder.ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(controllerFeatureProvider));
}

/// <summary>
/// Discovers DI registrable services in the assemblies marked for discovery.
/// </summary>
Expand Down
53 changes: 53 additions & 0 deletions src/JsonApiDotNetCore/Configuration/JsonApiControllerGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Controllers;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;

namespace JsonApiDotNetCore.Configuration
{
public class JsonApiControllerGenerator : IJsonApiControllerGenerator
{
private readonly IResourceGraph _resourceGraph;
private readonly Type _controllerOpenType;
private readonly Type _baseControllerOpenType;

public JsonApiControllerGenerator(IResourceGraph resourceGraph)
{
_resourceGraph = resourceGraph ?? throw new ArgumentNullException(nameof(resourceGraph));
_controllerOpenType = typeof(JsonApiController<,>);
_baseControllerOpenType = typeof(BaseJsonApiController<,>);
}

public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var exposedTypes = parts.SelectMany(part => ((AssemblyPart)part).Types).ToList();
foreach (var resourceContext in _resourceGraph.GetResourceContexts())
{
RegisterControllerForResource(feature, resourceContext, exposedTypes);
}
}


private void RegisterControllerForResource(ControllerFeature feature, ResourceContext resourceContext, List<TypeInfo> exposedTypes)
{
if (resourceContext != null && !resourceContext.ResourceType.IsAbstract)
{
var existingControllerType = _baseControllerOpenType.MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType).GetTypeInfo();
if (!exposedTypes.Any(exposedType => existingControllerType.IsAssignableFrom(exposedType)))
{
var controllerType = GetControllerType(resourceContext);
feature.Controllers.Add(controllerType);
}
}
}

protected virtual TypeInfo GetControllerType(ResourceContext resourceContext)
{
var controllerType = _controllerOpenType.MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType).GetTypeInfo();
return controllerType;
}
}
}
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public sealed class JsonApiOptions : IJsonApiOptions
}
};

/// <inheritdoc />
public bool AutoGenerateControllers { get; set; } = true;

// Workaround for https://github.com/dotnet/efcore/issues/21026
internal bool DisableTopPagination { get; set; }
internal bool DisableChildrenPagination { get; set; }
Expand Down
36 changes: 0 additions & 36 deletions src/JsonApiDotNetCore/Controllers/JsonApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,6 @@ public JsonApiController(
: base(options, loggerFactory, resourceService)
{ }

/// <inheritdoc />
public JsonApiController(
IJsonApiOptions options,
ILoggerFactory loggerFactory,
IGetAllService<TResource, TId> getAll = null,
IGetByIdService<TResource, TId> getById = null,
IGetSecondaryService<TResource, TId> getSecondary = null,
IGetRelationshipService<TResource, TId> getRelationship = null,
ICreateService<TResource, TId> create = null,
IAddToRelationshipService<TResource, TId> addToRelationship = null,
IUpdateService<TResource, TId> update = null,
ISetRelationshipService<TResource, TId> setRelationship = null,
IDeleteService<TResource, TId> delete = null,
IRemoveFromRelationshipService<TResource, TId> removeFromRelationship = null)
: base(options, loggerFactory,getAll, getById, getSecondary, getRelationship, create, addToRelationship, update,
setRelationship, delete, removeFromRelationship)
{ }

/// <inheritdoc />
[HttpGet]
public override async Task<IActionResult> GetAsync(CancellationToken cancellationToken)
Expand Down Expand Up @@ -127,23 +109,5 @@ public JsonApiController(
IResourceService<TResource, int> resourceService)
: base(options, loggerFactory, resourceService)
{ }

/// <inheritdoc />
public JsonApiController(
IJsonApiOptions options,
ILoggerFactory loggerFactory,
IGetAllService<TResource, int> getAll = null,
IGetByIdService<TResource, int> getById = null,
IGetSecondaryService<TResource, int> getSecondary = null,
IGetRelationshipService<TResource, int> getRelationship = null,
ICreateService<TResource, int> create = null,
IAddToRelationshipService<TResource, int> addToRelationship = null,
IUpdateService<TResource, int> update = null,
ISetRelationshipService<TResource, int> setRelationship = null,
IDeleteService<TResource, int> delete = null,
IRemoveFromRelationshipService<TResource, int> removeFromRelationship = null)
: base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update,
setRelationship, delete, removeFromRelationship)
{ }
}
}
Loading