OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
Other
454 stars 161 forks source link

Set EDM model through Dependency Injection #103

Open FayssalLantema opened 3 years ago

FayssalLantema commented 3 years ago

Is it possible to set the EDM builder through dependency injection?

This is the situation I have it now:

services.AddOData(options => options.AddModel("odata", EdmModelBuilder.GetEdmModel()).Select().Filter().Expand().OrderBy().Count()).AddConvention<CustomRoutingConvention>();

The context is dynamic; it depends on a dynamic model based on a token in a request.

Is it possible to just leave the services.AddOData() and add the model later on through dependency injection?

xuzhg commented 3 years ago

@FayssalLantema Can this extension work for you?

https://github.com/OData/AspNetCoreOData/blob/master/src/Microsoft.AspNetCore.OData/ODataServiceCollectionExtensions.cs#L45

FayssalLantema commented 3 years ago

Thanks for your answer but this is not exactly what I am looking for.

I was looking for something like this method:

public ODataOptions AddModel(string prefix, IEdmModel model, Action<IContainerBuilder> configureAction);

But also this is not exactly the solution.

We're building a microservice which is part of a multi-tenant application. The separation of the tenants are given through the JWT token. The model is dependent on the tenant and can change over time. This makes it impossible to give the model during startup, because the tenant is not known yet. So the EDM model that is required at this AddModel method is not known during startup.

DanielGlos commented 3 years ago

Hi, @FayssalLantema were you able to move forward with this somehow? I have exactly the same problem.

FayssalLantema commented 3 years ago

Hi @DanielGlos , sadly enough I haven't found a solution for this yet.

DanielGlos commented 3 years ago

Hi @xuzhg, could you suggest some solution how to achieve functionality as in this sample where EdmModel is generated per request. https://github.com/OData/ODataSamples/tree/master/WebApiClassic/DynamicEdmModelCreation

This extension was used there, IRouteBuilder.MapODataServiceRoute, to register EdmModel as scoped and generated each time new request came but this method is no logner availabe, also routing was changed to endpoint routing. What I'm trying to do now is to implement my own ODataRoutingMatcherPolicy and generate the model there but I'm not sure if this is the right approach. The thing is I need access to the HttpContext when generating the model. Also there is ODataTemplateTranslateContext used but this class cannot be instantiated since all it's constructors are marked internal.

I would appreciate any opinions on this. Are you planning to add some sort of support for dynamic model creation in the future?

xuzhg commented 3 years ago

@DanielGlos @FayssalLantema There's a new sample added, you can find here: https://github.com/OData/AspNetCoreOData/tree/master/sample/ODataDynamicModel

That's the same sample related to https://github.com/OData/ODataSamples/tree/master/WebApiClassic/DynamicEdmModelCreation

Please let me know your thought about the extensibility of 8.0.

DanielGlos commented 3 years ago

Hi @xuzhg I noticed one problem. When you try to access $metadata endpoint the EDM model is generated twice. Once the candidate is Get method from MetadataController and second time it's for Get method in HandleAllController. When it's trying to translate the context and create ODataPath the candidate is invalidated but this happens after the model is generated.

Can we just return Task.CompletedTask; here after merging route values and stop iterating over rest of the candidates?

                ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext);
                if (odataPath != null)
                {
                    odataFeature.PrefixName = metadata.Prefix;
                    odataFeature.Model = model;
                    odataFeature.Path = odataPath;

                    MergeRouteValues(translatorContext.UpdatedValues, candidate.Values);
                    **return Task.CompletedTask;**
                }

And one more question. Is it really necessary to take out the candidate by reference?

ref CandidateState candidate = ref candidates[i];

because this way the method cannot be made async even though it is returning Task. The candidate isn't passed to any method anyway.

xuzhg commented 3 years ago

@DanielGlos Yes, you can return the Task.CompletedTask.

For the reference, the operator [] is defined as:

public ref CandidateState this[int index]

so, i think we have to use "ref".

But, you can "break;" the for clause, right?