1

Closed

Delete fails if entity key is of type Guid

description

I send the following ODATA message:

_DELETE http://localhost:34852/Tags(guid'43e361ba-a8a0-4dbe-8474-dfcb5496c161') HTTP/1.1__
DataServiceVersion: 3.0
MaxDataServiceVersion: 3.0
Host: localhost:34852

I get back an error:

HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcY3BtU291cmNlc1xLbm93bGVkZ2UgTWFuYWdlclxEZXZlbG9wbWVudFxEYXRhU2VydmljZVByb3RvdHlwZVxXZWJBcGlEYXRhU2VydmljZVxQaW1zQ29uZmlnRGF0YVN2Y1xBQkIuUElNUy5TZXJ2ZXIuQ29uZmlnU2VydmljZVxUYWdzKGd1aWQnNDNlMzYxYmEtYThhMC00ZGJlLTg0NzQtZGZjYjU0OTZjMTYxJyk=?=
X-Powered-By: ASP.NET
Date: Tue, 05 Mar 2013 11:36:20 GMT
Content-Length: 561

{
"odata.error":{
"code":"","message":{
  "lang":"en-US","value":"The request is invalid."
},"innererror":{
  "message":"The parameters dictionary contains a null entry for parameter 'key' of non-nullable type 'System.Guid' for method 'Void Delete(System.Guid)' in 'ABB.PIMS.Server.ConfigService.Controllers.Base.StoreEntitySetController`2[ABB.PIMS.Data.Model.Domain.RTDB.Tag,System.Guid]'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.","type":"","stacktrace":""
}
}
}

The error happens before any of the(standard and custom) routing conventions are evaluated.
GetByKey works correctly.

I use the released version of WebApi OData (v4.0.30319). See attachment for relevant trace log part.

file attachments

Closed Apr 6, 2013 at 7:24 PM by HongmeiG
It looks like if you are using the correct guid format defined by the OData spec, and adding FromODataUri attribute should make this work.

If you are using some custom format of guid, then you need to write your own parameter binding to model bind the custom way you wanted to.

Please reactivate you are still blocked on this.

comments

h28669 wrote Mar 5, 2013 at 1:01 PM

NB. The underscore in front of DELETE method name is just due to some unwanted formatting. original message has correct method DELETE.

MrIanYates wrote Mar 28, 2013 at 6:59 AM

I think I'm having similar trouble but with navigation properties - the thing in common is the formatting of the GUID.

My guess is that if you make your URL
http://localhost:34852/Tags(43e361ba-a8a0-4dbe-8474-dfcb5496c161) (without the guid and the ' [quotes] ) then you'll be fine.

I'm doing a simple LINQ query from LINQPad to my simple service and if I filter with a .Where( entity => entity.MyKey = [insertGUIDHere] ), this gets translated into
http://localhost:61931/odata/Patients(guid'94c2a2fe-8a35-4c6a-9379-00f3c53a1d27') (my controller is PatientsController and it handles Patient entities) That fails in my web browser The parameters dictionary contains a null entry for parameter 'key' of non-nullable type 'System.Guid' for method 'System.Net.Http.HttpResponseMessage Get(System.Guid)' in 'Dox.Server.Controllers.PatientsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.

But if I take out the guid wrapping and make my URL
http://localhost:61931/odata/Patients(94c2a2fe-8a35-4c6a-9379-00f3c53a1d27) then it works a treat.

MrIanYates wrote Mar 28, 2013 at 6:59 AM

When I said "navigation properties" in my previous, that's how I first discovered the issue, but it really has nothing to do with navigation properties... Ignore that bit :) The rest is valid.

h28669 wrote Mar 28, 2013 at 6:23 PM

Since "guid'" is prefix for GUIDs as defined in ODATA spec then this looks like a bug and should be corrected.

raghuramn wrote Mar 28, 2013 at 7:18 PM

You must be missing the [FromODataUri] attribute on your Delete action key parameter.

MrIanYates wrote Mar 28, 2013 at 11:47 PM

I thought so too - I've been stepping through the framework code trying to see where the matching is being done. All of the samples use ints so I'm thinking I might adjust the ODataServiceSample to use GUIDs as a key for one of its entities to confirm that it's broken there and then I have something to go on.

MrIanYates wrote Mar 30, 2013 at 8:24 AM

So I've been stepping through the WebAPI source - all a very new thing to me as this is my first experience with WebAPI and I've already hit a problem - and have found that the ValueProviderResult.ConvertSimpleType(....) method is one place where this can be fixed.

It already has a couple of special case checks for things like an empty string and enums so I added another for cases where we have a string beginning with guid' and ending with a single ' and are mapping it to a Guid. I strip the leading guid' and trailing ' from the value parameter and let the method continue as normal.

Following through the code it seems I could implement a custom ModelBinder and ModelBinderProvider as in the one at the bottom of the page at http://forums.asp.net/t/1770906.aspx/1?How+do+I+add+a+custom+ModelBinder. I'm going to try that next - I suppose if this works then the WebAPI OData support itself should have such a ModelBinder provided out of the box to handle the Guid case.

MrIanYates wrote Apr 1, 2013 at 9:45 AM

I did some ModelBinder work but then dug in further and now understand a LOT more than I need to about how WebAPI and OData is implemented :)

I think my problem was pretty much all my fault in hindsight. I was initially passing malformed GUIDs to my service, and the only way I could make them work was to make my own method on my EntitySetController-derived class of the form

public override HttpResponseMessage Get(Guid key)
{
return base.Get(key);
}

I knew at the time this was crazy but it worked. That's before I realised I was using malformed GUIDs. Using correctly formatted OData GUIDs with that method in place confused the WebAPI binding infrastructure since the OData-style GUID didn't match what WebAPI itself (sans OData) was expecting.

After a lot of tracing through code I've found that the builtin OData model binding code doesn't support malformed GUIDs (with the guid' prefix) as the ODataLib library referenced by WebAPI just turns them into a string-wrapped int. For example the GUID 94abcdxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx turns into "94", which then can't be converted to a GUID.

Long (winding) story short: You don't have to override Get(EntityType Key), and if you find you do, then you're "doing it wrong" :) Once not overridden then GUIDs can be properly passed in. One error on my part broke other stuff that seemed unrelated at the time.

Bleh - off to have dinner :) Sorry to clutter up the thread.

h28669 wrote Apr 2, 2013 at 3:10 PM

Not sure if we are talking about the same thing. As you can see in initial comment, i try to perform a delete action. So if do override Delete method of class and not Get method.

This is the method I override
public override void Delete(Guid key)
{
...
}

But code flow never reaches the Delete method, because OData WebApi framework does not handle correctly a Guid prefixed by guid' (which is the "official" ODATA format for Guid's as per ODATA spec).

As I wrote in 1st post the GetByKey works as expected.