Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic O Data Controller does not return expected result

I am trying to create a generic OData controller. I did it based on this link.

Everything works fine, but http://localhost:65465/odata/Book returns data like

[
        {
            "Id": 999,
            "Title": null,
            "Author": "Author 999"
        }
]

instead of

{
    "@odata.context": "http://localhost:65465/odata/$metadata#Book",
    "value": [
        {
            "Id": 999,
            "Title": null,
            "Author": "Author 999"
        }]
}

On further investigation, I found that the edm model is not being set on the request object inside the get of the generic controller. It seems to be using non OData routing; although all OData filter, select, orderby work as expected.

Please let me know what I am missing that it is not setting the edm model on the request.

[Route("odata/[controller]")]
        [Produces("application/json")]
        public class CustomODataController<T> : ODataController where T : class, 
         ICustomEntity
        {
            
            [HttpGet]
            [EnableQuery]
            public IActionResult Get()
            {
                IEdmModel edmModel = Request.GetModel();
                return Ok(_storage.GetAll().AsQueryable());
               
            }
        }
        
        public class GenericTypeControllerFeatureProvider :IApplicationFeatureProvider<ControllerFeature>
        {
            public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
            {
                var currentAssembly = typeof(GenericTypeControllerFeatureProvider).Assembly;
                var candidates = currentAssembly.GetExportedTypes().Where(x => x.GetCustomAttributes<GeneratedControllerAttribute>().Any());
    
                foreach (var candidate in candidates)
                {
                
                    feature.Controllers.Add(typeof(CustomODataController<>).MakeGenericType(candidate).GetTypeInfo());
    
                }
            }
        }

        var mvcBuilder = services.AddMvc();
        mvcBuilder.AddMvcOptions(o => o.Conventions.Add(new GenericControllerRouteConvention()));
        mvcBuilder.ConfigureApplicationPartManager(c =>
        {
            c.FeatureProviders.Add(new GenericTypeControllerFeatureProvider());
        });
        
        app.UseMvc(b =>
        {
            GetEdmModel(), new DefaultODataPathHandler(), 
            routingConventions);
            b.MapODataServiceRoute("ODataRoutes", "odata", GetEdmModel());
            b.Expand().Select().Count().OrderBy().Filter();
            b.EnableDependencyInjection();
        });
    
        private IEdmModel GetEdmModel()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Book>(nameof(Book)).EntityType.Filter().OrderBy().Select();
            var edmModel =   builder.GetEdmModel();
            return edmModel;
        }
        
        
        public class GenericControllerRouteConvention : IControllerModelConvention
        {
            public void Apply(ControllerModel controller)
            {
                if (controller.ControllerType.IsGenericType)
                {
                    var genericType = controller.ControllerType.GenericTypeArguments[0];
                    
                    controller.Selectors.Add(new SelectorModel
                    {
                        AttributeRouteModel = new AttributeRouteModel(new RouteAttribute($"odata/{genericType.Name}"))
    
                    });
    
                }
            }
        }
like image 276
Asif Avatar asked Oct 16 '25 14:10

Asif


1 Answers

I played it with different options and finally was able to make it working using an attribute on the generic controller

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public class GenericControllerNameConventionAttribute : Attribute, IControllerModelConvention
    {
        public void Apply(ControllerModel controller)
        {
            if (controller.ControllerType.GetGenericTypeDefinition() !=
                typeof(CustomODataController<>))
            {
                // Not a GenericController, ignore.
                return;
            }

            var entityType = controller.ControllerType.GenericTypeArguments[0];
            controller.ControllerName = entityType.Name;
        }
    }


    [Produces("application/json")]
    [GenericControllerNameConvention]

    public class CustomODataController<T> : ODataController where T : class, IDataEntity
    {
        private Storage<T> _storage;

        public CustomODataController(Storage<T> storage)
        {
            _storage = storage;

        }

        [HttpGet]
        [EnableQuery]
        public IActionResult Get()
        {
            return Ok(_storage.GetAll().AsQueryable());

        }


        [EnableQuery]
        public SingleResult<T> Get([FromODataUri] int key)
        {
            return SingleResult.Create(_storage.GetAll().Where( n => n.Id == key).AsQueryable());
        }

        [HttpPost]
        [EnableQuery]
        public IActionResult Post([FromBody]T value)
        {
            _storage.AddOrUpdate(value.Id, value);
            return Created(value);
        }


        [HttpPut]
        [EnableQuery]
        public IActionResult Put([FromODataUri] int key, [FromBody]T value)
        {
            _storage.AddOrUpdate(key, value);
            return Updated(value);
        }


        [HttpPatch]
        [EnableQuery]
        public IActionResult Patch([FromODataUri] int key, [FromBody] Delta<T> value)
        {
            var existing = _storage.GetById(key);
            if (existing == null)
            {
                return NotFound();
            }
            value.Patch(existing);
            _storage.AddOrUpdate(key, existing);
            return Updated(existing);
        }


        [HttpDelete]
        [EnableQuery]
        public IActionResult Delete([FromODataUri] int key)
        {
            _storage.Delete(key);
            return NoContent();
        }



    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(typeof(Storage<>));
        services.AddOData();

        var mvcBuilder = services.AddMvc();

        mvcBuilder.ConfigureApplicationPartManager(c =>
        {
            c.FeatureProviders.Add(new GenericTypeControllerFeatureProvider());
        });

        mvcBuilder.AddJsonOptions(opt =>
            {
                opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            });



    }
like image 123
Asif Avatar answered Oct 19 '25 02:10

Asif



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!