Skip to content

Commit 96a6570

Browse files
committed
add overridable controllers
1 parent 62fefff commit 96a6570

File tree

13 files changed

+292
-112
lines changed

13 files changed

+292
-112
lines changed
Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,39 @@
11
using System;
22
using System.Collections.Generic;
3-
using AutoMapper;
3+
using JsonApiDotNetCore.Abstractions;
44

55
namespace JsonApiDotNetCore.Configuration
66
{
77
public interface IJsonApiModelConfiguration
88
{
9+
/// <summary>
10+
/// The database context to use
11+
/// </summary>
12+
/// <typeparam name="T"></typeparam>
13+
/// <exception cref="ArgumentException"></exception>
914
void UseContext<T>();
15+
16+
/// <summary>
17+
/// The request namespace.
18+
/// </summary>
19+
/// <param name="ns"></param>
20+
/// <example>api/v1</example>
1021
void SetDefaultNamespace(string ns);
11-
void DefineResourceMapping(Action<Dictionary<Type,Type>> mapping);
22+
23+
/// <summary>
24+
/// Define explicit mapping of a model to a class that implements IJsonApiResource
25+
/// </summary>
26+
/// <param name="modelType"></param>
27+
/// <param name="resourceType"></param>
28+
/// <exception cref="ArgumentException"></exception>
29+
void AddResourceMapping(Type modelType, Type resourceType);
30+
31+
/// <summary>
32+
/// Specifies a controller override class for a particular model type.
33+
/// </summary>
34+
/// <param name="modelType"></param>
35+
/// <param name="controllerType"></param>
36+
/// <exception cref="ArgumentException"></exception>
37+
void UseController(Type modelType, Type controllerType);
1238
}
1339
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Reflection;
3+
using JsonApiDotNetCore.Routing;
4+
using Microsoft.EntityFrameworkCore;
5+
using System.Linq;
6+
using AutoMapper;
7+
using JsonApiDotNetCore.Abstractions;
8+
using JsonApiDotNetCore.Attributes;
9+
10+
namespace JsonApiDotNetCore.Configuration
11+
{
12+
public class JsonApiConfigurationBuilder
13+
{
14+
private readonly Action<IJsonApiModelConfiguration> _configurationAction;
15+
private JsonApiModelConfiguration Config { get; set; }
16+
17+
public JsonApiConfigurationBuilder(Action<IJsonApiModelConfiguration> configurationAction)
18+
{
19+
Config = new JsonApiModelConfiguration();
20+
_configurationAction = configurationAction;
21+
}
22+
23+
public JsonApiModelConfiguration Build()
24+
{
25+
Config = new JsonApiModelConfiguration();
26+
_configurationAction.Invoke(Config);
27+
CheckIsValidConfiguration();
28+
LoadModelRoutesFromContext();
29+
SetupResourceMaps();
30+
return Config;
31+
}
32+
33+
private void CheckIsValidConfiguration()
34+
{
35+
if (Config.ContextType == null)
36+
throw new NullReferenceException("DbContext is not specified");
37+
}
38+
39+
private void LoadModelRoutesFromContext()
40+
{
41+
// Assumption: all DbSet<> types should be included in the route list
42+
var properties = Config.ContextType.GetProperties().ToList();
43+
44+
properties.ForEach(property =>
45+
{
46+
if (property.PropertyType.GetTypeInfo().IsGenericType &&
47+
property.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
48+
{
49+
50+
var modelType = property.PropertyType.GetGenericArguments()[0];
51+
52+
var route = new RouteDefinition
53+
{
54+
ModelType = modelType,
55+
PathString = RouteBuilder.BuildRoute(Config.Namespace, property.Name),
56+
ContextPropertyName = property.Name
57+
};
58+
59+
Config.Routes.Add(route);
60+
}
61+
});
62+
}
63+
64+
private void SetupResourceMaps()
65+
{
66+
LoadDefaultResourceMaps();
67+
var mapConfiguration = new MapperConfiguration(cfg =>
68+
{
69+
foreach (var definition in Config.ResourceMapDefinitions)
70+
{
71+
cfg.CreateMap(definition.Key, definition.Value);
72+
}
73+
});
74+
75+
Config.ResourceMapper = mapConfiguration.CreateMapper();
76+
}
77+
78+
private void LoadDefaultResourceMaps()
79+
{
80+
var resourceAttribute = typeof(JsonApiResourceAttribute);
81+
var modelTypes = Assembly.GetEntryAssembly().DefinedTypes.Where(t => t.GetCustomAttributes(resourceAttribute).Count() == 1);
82+
83+
foreach (var modelType in modelTypes)
84+
{
85+
var resourceType = ((JsonApiResourceAttribute)modelType.GetCustomAttribute(resourceAttribute)).JsonApiResourceType;
86+
87+
// do not overwrite custom definitions
88+
if(!Config.ResourceMapDefinitions.ContainsKey(modelType.UnderlyingSystemType))
89+
{
90+
Config.ResourceMapDefinitions.Add(modelType.UnderlyingSystemType, resourceType);
91+
}
92+
}
93+
}
94+
}
95+
}

JsonApiDotNetCore/Configuration/JsonApiModelConfiguration.cs

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Linq;
44
using System.Reflection;
55
using AutoMapper;
6+
using JsonApiDotNetCore.Abstractions;
7+
using JsonApiDotNetCore.Controllers;
68
using JsonApiDotNetCore.JsonApi;
79
using JsonApiDotNetCore.Routing;
810
using Microsoft.AspNetCore.Http;
@@ -17,58 +19,36 @@ public class JsonApiModelConfiguration : IJsonApiModelConfiguration
1719
public Type ContextType { get; set; }
1820
public List<RouteDefinition> Routes = new List<RouteDefinition>();
1921
public Dictionary<Type, Type> ResourceMapDefinitions = new Dictionary<Type, Type>();
22+
public Dictionary<Type, Type> ControllerOverrides = new Dictionary<Type, Type>();
2023

2124
public void SetDefaultNamespace(string ns)
2225
{
2326
Namespace = ns;
2427
}
2528

26-
public void DefineResourceMapping(Action<Dictionary<Type,Type>> mapping)
29+
// TODO: change to AddResourceMapping(Type, Type)
30+
public void AddResourceMapping(Type modelType, Type resourceType)
2731
{
28-
mapping.Invoke(ResourceMapDefinitions);
32+
if (!resourceType.GetInterfaces().Contains(typeof(IJsonApiResource)))
33+
throw new ArgumentException("Specified type does not implement IJsonApiResource", nameof(resourceType));
2934

30-
var mapConfiguration = new MapperConfiguration(cfg =>
31-
{
32-
foreach (var definition in ResourceMapDefinitions)
33-
{
34-
cfg.CreateMap(definition.Key, definition.Value);
35-
}
36-
});
37-
38-
ResourceMapper = mapConfiguration.CreateMapper();
35+
ResourceMapDefinitions.Add(modelType, resourceType);
3936
}
4037

41-
public void UseContext<T>()
38+
public void UseController(Type modelType, Type controllerType)
4239
{
43-
// TODO: assert the context is of type DbContext
44-
ContextType = typeof(T);
45-
LoadModelRoutesFromContext();
40+
if(!controllerType.GetInterfaces().Contains(typeof(IJsonApiController)))
41+
throw new ArgumentException("Specified type does not implement IJsonApiController", nameof(controllerType));
42+
43+
ControllerOverrides[modelType] = controllerType;
4644
}
4745

48-
private void LoadModelRoutesFromContext()
46+
public void UseContext<T>()
4947
{
50-
// Assumption: all DbSet<> types should be included in the route list
51-
var properties = ContextType.GetProperties().ToList();
52-
53-
properties.ForEach(property =>
54-
{
55-
if (property.PropertyType.GetTypeInfo().IsGenericType &&
56-
property.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
57-
{
58-
59-
var modelType = property.PropertyType.GetGenericArguments()[0];
60-
61-
var route = new RouteDefinition
62-
{
63-
ModelType = modelType,
64-
PathString = RouteBuilder.BuildRoute(Namespace, property.Name),
65-
ContextPropertyName = property.Name
66-
};
48+
ContextType = typeof(T);
6749

68-
Routes.Add(route);
69-
}
70-
});
50+
if (!typeof(DbContext).IsAssignableFrom(ContextType))
51+
throw new ArgumentException("Context Type must derive from DbContext", nameof(T));
7152
}
72-
7353
}
7454
}

JsonApiDotNetCore/Controllers/ControllerBuilder.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
25
using JsonApiDotNetCore.Abstractions;
36
using JsonApiDotNetCore.Data;
47

@@ -13,9 +16,36 @@ public ControllerBuilder(JsonApiContext context)
1316
_context = context;
1417
}
1518

16-
public JsonApiController BuildController()
19+
public IJsonApiController BuildController()
1720
{
18-
return new JsonApiController(_context, new ResourceRepository(_context));
21+
var overrideController = GetOverrideController();
22+
return overrideController ?? new JsonApiController(_context, new ResourceRepository(_context));
23+
}
24+
25+
public IJsonApiController GetOverrideController()
26+
{
27+
Type controllerType;
28+
return _context.Configuration.ControllerOverrides.TryGetValue(_context.GetEntityType(), out controllerType) ?
29+
InstantiateController(controllerType) : null;
30+
}
31+
32+
private IJsonApiController InstantiateController(Type controllerType)
33+
{
34+
var constructor = controllerType.GetConstructors()[0];
35+
var parameters = constructor.GetParameters();
36+
var services =
37+
parameters.Select(param => GetService(param.ParameterType)).ToArray();
38+
return (IJsonApiController) Activator.CreateInstance(controllerType, services);
39+
}
40+
41+
private object GetService(Type serviceType)
42+
{
43+
if(serviceType == typeof(ResourceRepository))
44+
return new ResourceRepository(_context);
45+
if (serviceType == typeof(JsonApiContext))
46+
return _context;
47+
48+
return _context.HttpContext.RequestServices.GetService(serviceType);
1949
}
2050
}
2151
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using System.Reflection;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
namespace JsonApiDotNetCore.Controllers
6+
{
7+
public interface IJsonApiController
8+
{
9+
ObjectResult Delete(string id);
10+
ObjectResult Get();
11+
ObjectResult Get(string id);
12+
ObjectResult Patch(string id, Dictionary<PropertyInfo, object> entityPatch);
13+
ObjectResult Post(object entity);
14+
}
15+
}

JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace JsonApiDotNetCore.Controllers
88
{
9-
public class JsonApiController
9+
public class JsonApiController : IJsonApiController
1010
{
1111
protected readonly JsonApiContext JsonApiContext;
1212
private readonly ResourceRepository _resourceRepository;
@@ -17,16 +17,16 @@ public JsonApiController(JsonApiContext jsonApiContext, ResourceRepository resou
1717
_resourceRepository = resourceRepository;
1818
}
1919

20-
public ObjectResult Get()
20+
public virtual ObjectResult Get()
2121
{
2222
var entities = _resourceRepository.Get();
23-
if(entities == null || entities.Count == 0) {
23+
if(entities == null) {
2424
return new NotFoundObjectResult(null);
2525
}
2626
return new OkObjectResult(entities);
2727
}
2828

29-
public ObjectResult Get(string id)
29+
public virtual ObjectResult Get(string id)
3030
{
3131
var entity = _resourceRepository.Get(id);
3232
if(entity == null) {
@@ -35,14 +35,14 @@ public ObjectResult Get(string id)
3535
return new OkObjectResult(entity);
3636
}
3737

38-
public ObjectResult Post(object entity)
38+
public virtual ObjectResult Post(object entity)
3939
{
4040
_resourceRepository.Add(entity);
4141
_resourceRepository.SaveChanges();
4242
return new CreatedResult(JsonApiContext.HttpContext.Request.Path, entity);
4343
}
4444

45-
public ObjectResult Patch(string id, Dictionary<PropertyInfo, object> entityPatch)
45+
public virtual ObjectResult Patch(string id, Dictionary<PropertyInfo, object> entityPatch)
4646
{
4747
var entity = _resourceRepository.Get(id);
4848
if(entity == null) {
@@ -55,7 +55,7 @@ public ObjectResult Patch(string id, Dictionary<PropertyInfo, object> entityPatc
5555
return new OkObjectResult(entity);
5656
}
5757

58-
public ObjectResult Delete(string id)
58+
public virtual ObjectResult Delete(string id)
5959
{
6060
_resourceRepository.Delete(id);
6161
_resourceRepository.SaveChanges();

JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,10 @@ public static class IServiceCollectionExtensions
1010
{
1111
public static void AddJsonApi(this IServiceCollection services, Action<IJsonApiModelConfiguration> configurationAction)
1212
{
13-
var config = new JsonApiModelConfiguration();
14-
configurationAction.Invoke(config);
15-
16-
if (config.ResourceMapper == null)
17-
{
18-
config.ResourceMapper = new MapperConfiguration(cfg => {}).CreateMapper();
19-
}
20-
services.AddSingleton(_ => new Router(config));
13+
var configBuilder = new JsonApiConfigurationBuilder(configurationAction);
14+
var config = configBuilder.Build();
15+
IRouter router = new Router(config);
16+
services.AddSingleton(_ => router);
2117
}
2218
}
2319
}

JsonApiDotNetCore/Routing/Router.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private void CallController()
4848
SendResponse(result);
4949
}
5050

51-
private ObjectResult ActivateControllerMethod(JsonApiController controller)
51+
private ObjectResult ActivateControllerMethod(IJsonApiController controller)
5252
{
5353
var route = _jsonApiContext.Route;
5454
switch (route.RequestMethod)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using JsonApiDotNetCore.Controllers;
2+
using JsonApiDotNetCore.Abstractions;
3+
using JsonApiDotNetCore.Data;
4+
using JsonApiDotNetCoreExample.Data;
5+
using Microsoft.AspNetCore.Mvc;
6+
using System.Linq;
7+
8+
namespace JsonApiDotNetCoreExample.Controllers
9+
{
10+
public class TodoItemsController : JsonApiController, IJsonApiController
11+
{
12+
private readonly ApplicationDbContext _dbContext;
13+
14+
public TodoItemsController(JsonApiContext jsonApiContext, ResourceRepository resourceRepository, ApplicationDbContext applicationDbContext) : base(jsonApiContext, resourceRepository)
15+
{
16+
_dbContext = applicationDbContext;
17+
}
18+
19+
public override ObjectResult Get()
20+
{
21+
return new OkObjectResult(_dbContext.TodoItems.ToList());
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)