Skip to content

OData.Client: The HttpMethod must be GET, POST or DELETE. (Parameter 'httpMethod') #3249

Open
@rpallares

Description

@rpallares

Note that I hesitate between creating a Bug or a Feature Request (both ?).

I try to extend the OData client to call Property Routing.
Sadly it doesn't look supported by the client. Probably because it's hard to know what properties are effectively supported by the API.
So I try to extend the client to support these calls. And finally I'm blocked because this exception throws at client side:

System.ArgumentException The HttpMethod must be GET, POST or DELETE. (Parameter 'httpMethod')

I consider it's a bug as the client should be extensible, and this is a hard extensibility limitation.

Here is the complete callstack:

System.ArgumentException
The HttpMethod must be GET, POST or DELETE. (Parameter 'httpMethod')
   at Microsoft.OData.Client.DataServiceContext.ValidateExecuteParameters[TElement](Uri& requestUri, String httpMethod, Nullable`1& singleResult, List`1& bodyOperationParameters, List`1& uriOperationParameters, OperationParameter[] operationParameters)
   at Microsoft.OData.Client.DataServiceContext.InnerBeginExecute[TElement](Uri requestUri, AsyncCallback callback, Object state, String httpMethod, String method, Nullable`1 singleResult, OperationParameter[] operationParameters)
   at Microsoft.OData.Client.DataServiceContext.BeginExecute(Uri requestUri, AsyncCallback callback, Object state, String httpMethod, OperationParameter[] operationParameters)
   at Microsoft.OData.Client.DataServiceContext.<>c__DisplayClass239_0.<ExecuteAsync>b__0(AsyncCallback callback, Object state)
   at Microsoft.OData.Client.DataServiceContext.<>c__DisplayClass296_0`1.<FromAsync>b__0(AsyncCallback callback, Object state)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncImpl(Func`3 beginMethod, Func`2 endFunction, Action`1 endAction, Object state, TaskCreationOptions creationOptions)
   at System.Threading.Tasks.TaskFactory`1.FromAsync(Func`3 beginMethod, Func`2 endMethod, Object state)
   at Microsoft.OData.Client.DataServiceContext.FromAsync[TResult](Func`3 beginMethod, Func`2 endMethod, CancellationToken cancellationToken)
   at Microsoft.OData.Client.DataServiceContext.ExecuteAsync(Uri requestUri, String httpMethod, CancellationToken cancellationToken, OperationParameter[] operationParameters)

Assemblies affected

Microsoft.OData.Client 7.21.6
But it seems all clients are impacted

Steps to Reproduce

  1. Having an API that expose a standard property endpoint (PUT http verb)
  2. Create DataServicePropertyUpdateQuery class which is almost copy paste of DataServiceActionQuery with HttpPut verb that satisfy OData protocol
    public sealed class DataServicePropertyUpdateQuery
    {
        private readonly DataServiceContext _context;
        private readonly BodyOperationParameter _parameter;
    
        public DataServicePropertyUpdateQuery(DataServiceContext context, string requestUriString, object value)
        {
            _context = context;
            RequestUri = new Uri(requestUriString);
            _parameter = new BodyOperationParameter("value", value);
        }
    
        public Uri RequestUri { get; private set; }
        public OperationResponse Execute() =>
            _context.Execute(RequestUri, HttpMethod.Put.Method, _parameter);
    
        public IAsyncResult BeginExecute(AsyncCallback callback, object state)
             => _context.BeginExecute(RequestUri, callback, state, HttpMethod.Put.Method, _parameter);
    
        public Task<OperationResponse> ExecuteAsync() => ExecuteAsync(CancellationToken.None);
    
        public Task<OperationResponse> ExecuteAsync(CancellationToken cancellationToken)
             => _context.ExecuteAsync(RequestUri, HttpMethod.Put.Method, cancellationToken, _parameter);
    
        public OperationResponse EndExecute(IAsyncResult asyncResult)
             => _context.EndExecute(asyncResult);
    }
  3. Add an extension method to call the endpoint from the client;
    public static class DataServiceQuerySingleExtensions
    {
        public static DataServicePropertyUpdateQuery UpdateMyProperty(this DataServiceQuerySingle<MyEntity> source, MyProperty newValue)
        {
            return new DataServicePropertyUpdateQuery(
                source.Context,
                source.AppendRequestUri(nameof(MyProperty)),
                newValue
            );
        }
    }
  4. Usage:
MyProperty newValue = MyProperty.CreateValue();

var serviceRoot = "https://services.odata.org/V4/MyService/";
var context = new DefaultContainer(new Uri(serviceRoot));

var result = await context.MyEntities.ByKey("somekey")
    .UpdateMyProperty(newValue)
    .ExecuteAsync();

Expected behaviour

  • The client generated by the container should be able to execute property update requests using Http Put verb.
  • DataServiceContext.ValidateExecuteParameters should accept the Http verb Put as it's a valid OData protocol verb
  • Optionally DataServicePropertyUpdateQuery should be part of Microsoft.Odata.Client library may be with a better interface allowing client to easilly call Property update endpoints

Actual behaviour

  • The code above fail with System.ArgumentException
  • DataServiceContext.ValidateExecuteParameters method is private and cannot be overridden

Additional details

  • The code above works if I expose the property update endpoint using POST verb, but it's not compliant with specification: OData 4.01/11.4.9.1 Update a Primitive Property
  • I don't need it right now but the protocol specifiy it's possible to update a complex property using Http PATCH verb

I'd be very happy if a new release of the client (v7 and v8) could be published with DataServiceContext.ValidateExecuteParameters overridable.
And may be an more long term issue created for the next major to properly address that entire topic

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions