Assume we create a sample app, let's call it MyOdataApp: I am unable to dynamically create controllers for these two models which you see in the Program.cs, I also have no errors to tell my where I have gone awry. What's in these models is irrelavant, let's assume they are: Order & LineItem
namespace MyOdataApp.Models
{
public class Order
{
public string OrderId { get; set; } = null!;
public string? CustomerName { get; set; }
public string? BillingAddress { get; set; }
public string? ShippingAddress { get; set; }
public decimal? SubTotal { get; set; }
public string? Currency { get; set; }
public double? ExchangeRate { get; set; }
public string? Status { get; set; }
public DateTimeOffset CreateTs { get; set; }
public virtual ICollection <LineItem> LineItems { get; set; }
}
public class LineItem
{
public string LineItemId { get; set; } = null!;
public string? OrderId { get; set; }
public int? LineIndex { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public int? Quantity { get; set; }
public decimal? UnitPrice { get; set; }
public double? Discount { get; set; }
public DateTimeOffset CreateTs { get; set; }
public virtual Order? Order { get; set; }
}
}
Here's my code: Program.cs
static IEdmModel GetEdmModel()
{
Microsoft.OData.ModelBuilder.ODataConventionModelBuilder builder = new();
builder.EntitySet(nameof(MyOdataApp.Models.Order));
builder.EntitySet(nameof(MyOdataApp.Models.LineItem));
return builder.GetEdmModel();
}
builder.Services.AddControllers()
.AddOData(opt => opt.EnableQueryFeatures().AddRouteComponents("odata", GetEdmModel()).Expand())
.ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new Controllers.GenericControllerFeatureProvider()));
OdataTemplateController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.EntityFrameworkCore;
namespace Controllers;
[GenericControllerName]
[Route("odata/[Controller]")]
public abstract class OdataTemplateController<T> : Microsoft.AspNetCore.OData.Routing.Controllers.ODataController
{
public readonly DbContext _context;
public readonly ILogger<T> _logger;
public OdataTemplateController(ILogger<T> logger, DbContext context)
{
_context = context;
_logger = logger;
}
[EnableQuery]
public virtual IQueryable Get() where T : class => _context.Set().AsQueryable();
}
GenericControllerFeature.cs
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Reflection;
namespace Controllers;
public class GenericControllerFeatureProvider : IApplicationFeatureProvider
{
public void PopulateFeature(IEnumerable parts, ControllerFeature feature)
{
var sysParts = parts.First() as dynamic;
foreach (TypeInfo entityType in sysParts.Types)
{
if (entityType.FullName!.StartsWith("MyOdataApp.Models") && !feature.Controllers.Any(t => t.Name == entityType.Name))
{
feature.Controllers.Add(typeof(OdataTemplateController<>)
.MakeGenericType(entityType.AsType())
.GetTypeInfo());
}
}
}
}
GenericControllerNameAttribute.cs
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Controllers;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericControllerNameAttribute : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.Name == "OdataTemplateController`1")
controller.ControllerName = controller.ControllerType.GenericTypeArguments[0].Name;
}
}
Now, aside from some very poor choices like using the (literal) Name ("OdataTemplateController`1") of the ControllerType and taking shortcuts like StartsWith "MyOdataApp.Models" which you can beat/ whip me for later... ...What am I doing wrong?
I noticed the Edm relationships are perfect at /$metadata but the (ControllerFeature) features is only injecting the MetadataController and nothing else. Could this be the problem?
Please help :)