I need an API that would expose all my DB tables as an OData
API's. For this I have used scaffold db to generate all the existing models, and I have already managed to create a correct EDM
model using reflection for the OData
to work with. I have also created a generic controller that I can use as a base controller for each of my models. And it looks like this:
public class GenericController<TEntity> : ODataController
where TEntity : class, new()
{
private readonly DbContext _context;
public GenericController(DbContext context)
{
_context = context;
}
[HttpGet]
[EnableQuery(PageSize = 1000)]
[Route("odata/{Controller}")]
public async Task<ActionResult<IEnumerable<TEntity>>> GetObjects()
{
try
{
if (_context.Set<TEntity>() == null)
{
return NotFound();
}
var obj = await _context.Set<TEntity>().ToListAsync();
return Ok(obj);
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
}
I can use this controller as a base for each of my models by manually creating a controller per model like this:
[ApiController]
public class ModelController : GenericController<MyModel>
{
public ActiviteitenObjectsController(Dbcontext context) : base(context)
{
}
}
And it works fine with OData
filters and everything. But the problem is I have way too many tables to be able to manually create the controllers for every single one of them.
I know you can use app.MapGet("/", () => "Hello World!")
to map the endpoints to a delegate or even use HTTPContext
inside of it, but I can't figure out how to use it in my case, so that it would work with OData
as well. Are there any approaches I can use to solve my problem?
With reference from few links.
Add Helper
, GenericControllerNameAttribute
& GenericControllerFeatureProvider
.
Then decorate your controller with annotation [GenericControllerName]
.
Modify ConfigureServices
from Startup
and append .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new Controllers.GenericControllerFeatureProvider()));
after services.AddMvc()
or services.AddControllers()
or services.AddControllersWithViews()
whatever you have used.
Helper
will initialize all the DBSet
from your DbContext
and create dictionary with key as name of Model and/or name of property.
Helper.cs
public static class Helper
{
public static Dictionary<string, System.Type> modelDictionary = new Dictionary<string, System.Type>(System.StringComparer.OrdinalIgnoreCase);
static Helper()
{
var properties = typeof(DbContext).GetProperties();
foreach (var property in properties)
{
var setType = property.PropertyType;
var isDbSet = setType.IsGenericType && (typeof(DbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()));
if (isDbSet)
{
// suppose you have DbSet as below
// public virtual DbSet<Activity> Activities { get; set; }
// genericType will be typeof(Activity)
var genericType = setType.GetGenericArguments()[0];
// Use genericType.Name if you want to use route as class name, i.e. /OData/Activity
if (!modelDictionary.ContainsKey(genericType.Name))
{
modelDictionary.Add(genericType.Name, genericType);
}
}
}
}
}
GenericControllerNameAttribute.cs
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericControllerNameAttribute : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() == typeof(Generic2Controller<>))
{
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name;
}
}
}
GenericControllerFeatureProvider
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
// Get the list of entities that we want to support for the generic controller
foreach (var entityType in Helper.modelDictionary.Values.Distinct())
{
var typeName = entityType.Name + "Controller";
// Check to see if there is a "real" controller for this class
if (!feature.Controllers.Any(t => t.Name == typeName))
{
// Create a generic controller for this type
var controllerType = typeof(Generic2Controller<>).MakeGenericType(entityType).GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}
GenericController.cs
[GenericControllerName]
public class GenericController<TEntity> : ODataController
where TEntity : class, new()
{
// Your code
}
Modification in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add .ConfigureApplicationPartManager(...); with AddMvc or AddControllers or AddControllersWithViews based on what you already have used.
services.AddMvc()
.ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
//services.AddControllers()
// .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
//services.AddControllersWithViews()
// .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
}