OData / ODataSamples

Samples: For ODataLib, OData Web API, RESTier, etc.
http://odata.github.io/
Other
297 stars 223 forks source link

Unable to build OdataUntypedSample when upgraded to odata v7 #50

Open last-Programmer opened 8 years ago

last-Programmer commented 8 years ago

Hi,

I tried upgrading the odatauntypedsample project to odata libraries version 7 and i am getting build errors

Severity Code Description Project File Line Suppression State Error CS0234 The type or namespace name 'Library' does not exist in the namespace 'Microsoft.OData.Edm' (are you missing an assembly reference?) ODataUntypedSample D:\Balu\Sgc\ODatav4 Sample\ODataSamples-master\WebApi\v4\ODataUntypedSample\ODataUntypedSample\Controllers\ProductsController.cs 8 Active

Severity Code Description Project File Line Suppression State Error CS1061 'HttpRequestMessageProperties' does not contain a definition for 'Model' and no extension method 'Model' accepting a first argument of type 'HttpRequestMessageProperties' could be found (are you missing a using directive or an assembly reference?) ODataUntypedSample D:\Balu\Sgc\ODatav4 Sample\ODataSamples-master\WebApi\v4\ODataUntypedSample\ODataUntypedSample\Controllers\ProductsController.cs 41 Active

Is there any breaking change with OData Libraries version 7.

Thanks

azachert commented 8 years ago

Same thing with DynamicEdmModelCreation. Is there a plan to update these samples?

smourier commented 8 years ago

I had the same problem. It seems the new packages contains (a lot of) breaking changes. I finally found a solution. Now that HttpRequestMessageProperties doesn't contain a Model property anymore, to get the model, you just need the new version (should come with the packages update) of HttpRequestMessageExtensions class that contains a GetModel extension method.

So I replaced

var model = Request.ODataProperties().Model;

by

var model = Request.GetModel();

Note I'm unable to find any reference of this change anywhere, and worst, I'm also unable to find the change in the open source available on the supposed repositories here: https://github.com/OData/WebApi/blob/master/OData/src/System.Web.Http.OData/Extensions/HttpRequestMessageExtensions.cs or anywhere else on the Internet :-(

jlkalberer commented 7 years ago

@smourier thanks for this.

Do you know of any way to set the model in the request?

joserito commented 7 years ago

Same question? How should we set the model now? Is there a different approach for this?

jlkalberer commented 7 years ago

@joserito - I think I ended up just using an older version of OData which isn't ideal...

last-Programmer commented 7 years ago

Any Update on this please?

smourier commented 7 years ago

About the initial question, my answer is still valid, you just have to change one line (replace Request.ODataProperties().Model by Request.GetModel()) in the sample, and it works with the newest nuget packages.

About the how to set the model in the request, well, you're still supposed to use routes, like what's done here in the sample:

HttpConfiguration configuration = new HttpConfiguration();
configuration.MapODataServiceRoute("odata", "odata", Model); // sets the model as a Service, available in (request) containers
builder.UseWebApi(configuration);

Note since my original answer, these changes are somewhat documented here (if you want to tweak the whole system): https://odata.github.io/WebApi/13-04-DependencyInjection/ and the code in System.Web.OData dependency that wasn't available back then is now here: https://github.com/OData/WebApi/blob/master/src/System.Web.OData/Extensions/HttpRequestMessageExtensions.cs

last-Programmer commented 7 years ago

We are using something similar to the code used in https://github.com/OData/ODataSamples/blob/master/WebApi/v4/DynamicEdmModelCreation/DynamicEdmModelCreation/CustomODataPathRouteConstraint.cs

HttpRequestMessageProperties odataProperties = request.ODataProperties(); odataProperties.Model = model; odataProperties.PathHandler = PathHandler; odataProperties.Path = path; odataProperties.RouteName = RouteName; odataProperties.RoutingConventions = RoutingConventions;

we can't set the model dynacmically with the new version 7.

Not sure how to achieve the same functionality.

smourier commented 7 years ago

You could post your question on StackOverflow I guess.

xuzhg commented 7 years ago

@rbmanian75 @smourier @jlkalberer @joserito @azachert

I just added a new ODataUntypedSample project to target the Web API OData v6.0 and ODataLib v7.0. Would you please try it out and let me know any problem.

last-Programmer commented 7 years ago

Thank you for the update.

It would be great if you could update this sample also https://github.com/OData/ODataSamples/blob/master/WebApi/v4/DynamicEdmModelCreation/DynamicEdmModelCreation/CustomODataPathRouteConstraint.cs in which the model is set dynamically.

because with odatalib 7 it is not possible to set the model dynamically.

Thank you

jlkalberer commented 7 years ago

Any update on this? Anyone figure out a solution for dynamic models?

lequangnguyenqn commented 7 years ago

I have a same problem using DynamicEdmModelCreation for version 7.0.0. Hope will have this update soon.

jlkalberer commented 7 years ago

I have something I think would work as a solution which I was using in conjunction with the dynamic EDM models -- but I can probably replace the dynamic EDM models with this. It basically updates the $filter server-side and removes any fields that aren't whitelisted.

Last night I was thinking of updating it a bit more (to support more than filtering on my Account model) and putting out an example project. Here's the basic code but it only filters on Account as a navigation property at the moment:

public class FilterQueryForRolesPropertyFilter : EnableQueryAttribute
{
    public FilterQueryForRolesPropertyFilter()
    {
        this.PageSize = 100;
        this.AllowedQueryOptions = AllowedQueryOptions.All;
    }

    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
        var result = base.ApplyQuery(queryable, queryOptions);

        var originalRequest = queryOptions.Request;

        Func<SelectExpandClause, ExpandedNavigationSelectItem, SelectExpandClause> fillTypesRecursive = null;
        fillTypesRecursive = (selectExpandClause, parent) =>
        {
            if (selectExpandClause == null)
            {
                return null;
            }

            List<SelectItem> selectItems = new List<SelectItem>();
            if (this.ShouldFilter(parent, typeof(Account)))
            {
                var edmType = parent.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType;
                if (edmType.TypeKind == EdmTypeKind.Collection)
                {
                    edmType = ((EdmCollectionType)edmType).ElementType.Definition;
                }

                var source = parent.NavigationSource;
                var parser = new ODataQueryOptionParser(
                    queryOptions.Context.Model,
                    edmType,
                    source,
                    // THIS IS THE MAGICAL FILTERING CODE!!!
                    new Dictionary<string, string> {{"$select", "id, userName"}}
                );

                return parser.ParseSelectAndExpand();
            }

            foreach (var selectItem in selectExpandClause.SelectedItems)
            {
                var expandedItem = selectItem as ExpandedNavigationSelectItem;
                if (expandedItem != null)
                {
                    expandedItem = new ExpandedNavigationSelectItem(
                                       expandedItem.PathToNavigationProperty,
                                       expandedItem.NavigationSource,
                                       fillTypesRecursive(expandedItem.SelectAndExpand, expandedItem),
                                       expandedItem.FilterOption,
                                       expandedItem.OrderByOption,
                                       expandedItem.TopOption,
                                       expandedItem.SkipOption,
                                       expandedItem.CountOption,
                                       expandedItem.SearchOption,
                                       expandedItem.LevelsOption);
                    selectItems.Add(expandedItem);
                }
                else
                {
                    //expandedItem.SelectAndExpand = fillTypesRecursive(expandedItem.SelectAndExpand);
                    selectItems.Add(selectItem);
                }

            }

            return new SelectExpandClause(selectItems, selectExpandClause.AllSelected);
        };

        var clause = originalRequest.ODataProperties().SelectExpandClause;
        if (clause != null)
        {
            originalRequest.ODataProperties().SelectExpandClause = fillTypesRecursive(clause, null);
        }

        return result;
    }

    private bool ShouldFilter(ExpandedNavigationSelectItem parent, Type typeToFilter)
    {
        if (parent == null)
        {
            return false;
        }

        var expandType = parent.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType;

        string expandedTypeName = null;
        // For single navigation properties
        if (expandType is EdmEntityType)
        {
            expandedTypeName = ((EdmEntityType)expandType).FullTypeName();
        }
        else if (expandType is EdmCollectionType)
        {
            expandedTypeName = ((EdmCollectionType)expandType).ElementType.Definition.FullTypeName();
        }

        return !string.IsNullOrWhiteSpace(expandedTypeName) && typeToFilter.FullName.Contains(expandedTypeName);
    }
}

I'll post an update with link to the code if I get to it. Personally, I'd really like to update my OData code to v7 so this should happen soonish

jlkalberer commented 7 years ago

Hey everyone, I got motivated over the weekend and I implemented a different way to filter the properties on entities by overriding the behavior of the EnableQueryAttribute instead of using dynamic EDM models. The code is a little rough and could probably be optimized a bit but it handles most of my use-cases.

Please pull the repo and try it out. Let me know if it's missing some features and send a pull request if you can think of any improvements.

Nkrb commented 6 years ago

@smourier I have the following error Error CS0234 The type or namespace name 'Library' does not exist in the namespace 'Microsoft.OData.Edm' (are you missing an assembly reference?) . what is the solution for this

Nkrb commented 6 years ago

@rbmanian75 @azachert @jlkalberer @joserito any one has workaround for this error. Error CS0234 The type or namespace name 'Library' does not exist in the namespace 'Microsoft.OData.Edm' (are you missing an assembly reference?)

mgernand commented 6 years ago

Hi folks! :-)

The new version of the OData Libraries use the default DI container to manage dependencies and services. The model (IEdmModel) is also registered as dependency (for a specific OData route).

To implement a model-per-request pattern (as in the DynamicEdmModelCreation sample) one has to implement a model factory that creates the IEdmModel for a OData route (in the sample for is for the given name of a datasource) for every request. The model to use must then be acquired from a scoped (per-request) container.

I need the dynamic model creation (model-per-request) feature and will look into fixing the sample soon. There are some more obstacles beside the dynamic model creation. I hope to look into the rest ver soon and keep up updated.

Cheers, Matt

See: https://github.com/OData/ODataSamples/issues/65#issuecomment-371591978

last-Programmer commented 6 years ago

Waiting to see the samples. We are stuck in upgrading our project for long time because of this issue.

mgernand commented 6 years ago

Hi folks,

I just added a pull request that fixes the dynamic model creation sample for v6:

Issue: #65 Pull Request: #67

Maybe this helps with this issue too.

Cheers, Matt

jlkalberer commented 6 years ago

So the key is just this line builder.AddService<IEdmModel>(ServiceLifetime.Scoped, sp =>?

I will give your code a go but I tried using dependency injection. The callback would only run once so the model wasn't being recreated... maybe my scope was just wrong or there is more to the example than I'm seeing.

last-Programmer commented 6 years ago

By seeing the sample i am trying to upgrade our project. We are using a custom odata query parser where we are parsing the $filter property like this

` HttpRequestMessageProperties odataProperties = this.Request.ODataProperties();

        ODataQueryOptionParser odataQueryOptionParser =
            new ODataQueryOptionParser(
                odataProperties.Model,
                collectionType.ElementType.Definition,
                odataProperties.Path.NavigationSource,
                new Dictionary<string, string>() { ["$filter"] = this.Parameters.Filter });`

Here we are using the odata model from oDataProperties(). We are not sure how to determine the model at this place where we are using custom parser to filter the data.

jlkalberer commented 6 years ago

@rbmanian75 - check out my example. I actually do this there. I think the code is outdated and has a few bugs but it will give you the general idea.

last-Programmer commented 6 years ago

I managed to get the model from the new method Request.GetModel(). Thanks

last-Programmer commented 6 years ago

The sample you have provided when hosted in asp.net gives the following error.(in our project we host odata in asp.net). The error happens in the line configuration.Routes.Remove(routeName); But when self hosted it works fine. Is there anyway we can fix this? ` This operation is not supported by 'HostedHttpRouteCollection'. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NotSupportedException: This operation is not supported by 'HostedHttpRouteCollection'.

Source Error:

Line 35: Line 36: ODataRoute odataRoute = new ODataRoute(route.RoutePrefix, new CustomODataPathRouteConstraint(routeName)); Line 37: configuration.Routes.Remove(routeName); Line 38: configuration.Routes.Add(routeName, odataRoute); Line 39:

Source File: C:\S\SGC\Sgc.Web\OData.Web\Handlers\ODataCustom\DynamicModelHelper.cs Line: 37

`

last-Programmer commented 6 years ago

To Solve the above mentioned issue there should be a way to specify the customodataroute and customodatapathrouteconstraint in mapodataserviceroute.

Please check this issue also https://github.com/OData/WebApi/issues/826

piyushparate1 commented 6 years ago

Hi everyone, In my case "$expand" is not working. Associate/Child records not returned in result. Can anyone help me or pass on some clue, Many thanks in advance.

Error details: https://stackoverflow.com/questions/50265218/expand-not-working-associate-child-records-not-returned-in-result-update Source code: https://github.com/piyushparate1/SampleODataUntyped

daviburg commented 5 years ago
      I managed to get the model from the new method Request.GetModel(). Thanks

I don't understand this comment. Request.GetModel() (and SetModel) methods were made obsolete long ago and replaced with the very ODataProperties().Model gettable-settable property which is here also removed albeit without a clear replacement.

There is the referred App_Start sample for providing a model on demand through the registered delegate. When we had a HttpRequestMessage it used to be possible to set the edm model for it, and I don't understand how to do the same here. My specific case is complicated by trying this in the context of a unit test for a service with dynamic EDM, upgrading the service from the older odata injection with gettable-settable property to the current approach.