POST to action method with arguments

May 16, 2012 at 5:39 PM
Edited May 16, 2012 at 5:40 PM

 

 We are adopting the .NET Restful Objects Server framework full scale. After a month, we are getting to the nitty gritty. Here is a quick question:

 This is after following your screencasts mighty carefully (fantastic, by the way)...
 When I do a POST to an action method with arguments (via Chrome addon Postman), I get the following error:
<?xml version="1.0" encoding="utf-8"?><Exception xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ExceptionType>System.Xml.XmlException</ExceptionType><Message>The data at the root level is invalid. Line 1, position 1.</Message><StackTrace>   at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)&#xD;
   at System.Xml.XmlUTF8TextReader.Read()&#xD;
   at System.Xml.XmlBaseReader.MoveToContent()&#xD;
   at System.Xml.Linq.XElement.Load(XmlReader reader, LoadOptions options)&#xD;
   at System.Net.Http.Formatting.XmlMediaTypeFormatter.&lt;&gt;c__DisplayClass4.&lt;OnReadFromStreamAsync&gt;b__3()&#xD;
   at System.Net.Http.Internal.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)</StackTrace></Exception>

Pretty generic.
and in the body, I have this:
{
orderNumber: {
value: "12345"
}
}

This is a cut and paste from what the REST API outputs (as your screencast suggests). Without any content in the body, it, of course complains of the lack of argument (invalidReason: mandatory).
POSTs to action methods without arguments, and GET methods work fine.
Any insights greatly appreciated.
Coordinator
May 16, 2012 at 6:47 PM

First, please check that you are using the very latest version of the NuGet packages.  (As it is still a pre-release, you'll need to check for updates using the Package Manager Console and include the '-pre' tag).  After we moved to the latest (Beta) of the Microsoft Web.API, we discovered that all the Post and Put actions requiring a body had stopped working.  This has since been fully fixed.  (It's also the reason why we've added nearly 400 end-to-end tests to the build, on top of all the existing unit tests, to make sure we're not caught out that way again).

If you are using the latest packages and still get the issue, it is possibly because your browsing tool is not specifying the Content-Type for the body, which should be application/json. 

BTW I'm very glad to hear you're using Restful Objects for .NET for real, and look forward to further feedback from you.

May 17, 2012 at 4:38 AM

Great suggestions. So, I did the following:

  1. Just to be sure, created a brand new Rest project
  2. Restfulobjects.server is at 0.15.0-beta and Restfulobjects.mvc is at 0.21.0-beta
  3. Nakedobjects assemblies (ide, programmingmodel, framework) are at 4.0.306.0
  4. Set content-type in POST request to application/json (was not doing that before)
  5. GET is fine and so are POST requests to methods without args.

POST to methods with args needs to be of the following syntax:

For a method with a string arg (userName), I got an error when I cut and pasted the following into the REST client 

{  userName: { value: "admin"}

}

This is how it appears when I follow the action link for this method via a browser.

It worked when I had the following in the request body

 {"userName": {"value": "admin"}}

Notice the enclosures in quotes.

 

Now, we are getting somewhere. Thanks for your help, Richard.

 

 

 

Coordinator
May 17, 2012 at 7:58 AM

Glad you got that working.  A couple of points:

1) You say that when you follow the action link it shows you the required parameters in this form: {  userName: { value: "admin"}}  (i.e. without quotes on the property names). I think that if you looked at the raw message (e.g. using Fiddler) you would see that the quotes are in fact there, but your browser client has helpfully(!) stripped them out.  This is annoying  -  I've encountered it on other clients also  -  because it means you can't just cut and paste the arguments template from the action link into the body, as you have found.

2) As you will know, JSON does require all properties to be in quotes, even though some people write it without them.  The Restful Objects spec says that an RO server must serve up correct JSON (i.e. with quotes) but should accept JSON without quotes (following Postels Law).  In the current Beta version of Restful Objects for .NET this is not the case  -  it does require strict JSON to be submitted in the body. This is because the parsing of the body is handled by the Microsoft Web.API - and in the current (Beta) version of that it is not possible for us to intervene. Based on the nightly-builds of the latter now available, it seems likely that by the time Web.API is released then there will be hooks that will allow Naked Objects to intercept the parsing of the body, in which case Restful Objects for .NET can then be made to tolerate missing quotes from property names.

Hope that helps.

Aug 18, 2012 at 4:50 AM

The above approach works when calling an action method with simple arguments, e.g. an int.

However, when attempting to call an action method like this:

public IQueryable<Order> GetAllOrdersForUser(User user)

In which "User" is itself a domain object, not a simple object like an int, how does one pass the user? Every attempt I have made to make this call -- and I have made a great many -- yields a 404 or some other kind of server error. The method works fine from the MVC app.

Since the action method returns IQueryable, it should be callable via GET, per RO documentation. I've tried a great variety of approaches, and read through the RO spec, but unless I am missing something it seems silent on this. I've tried GET and PUT, even though I think it should be a GET. I've tried passing an encoded, as well as a non-encoded, URL for the User object in question. I've tried passing the id for the User. I've tried passing the parameter in the query string in each of these ways, as well as in the body of a POST. I've set the content-type in the header to various things. I've looked at the RO source code, but it frankly looks like a major investment of time to understand that. I would be very grateful for any assistance on this. Thank you.

Aug 18, 2012 at 5:51 AM

Update: I have reviewed sections 2.9 and 2.10 of the Restful Objects specification, which seemingly address this question. Section 2.10 uses an example of calling an action without "invoke" in the URL, which seems odd. Nevertheless, I have tried what is recommended there, with the parameters encoded in the URL as a querystring, with and without invoke, with and without a trailing slash after the invoke, with Content-Type set to application/json (or missing), with and without trailing comma after the ref URL, but unfortunately I am getting nothing but error 400 thus far. Admittedly I have not tried every single combination of these thus far.

Developer
Aug 18, 2012 at 6:32 AM

this can be done: look at the formal arguments section. The URL of the user is passed as the value of the href in the map, and the whole map is url encoded and sent as the query string of the GET.

Sorry to be brief, sent from my phone

On Aug 18, 2012 5:50 AM, "jdubman" <notifications@codeplex.com> wrote:

From: jdubman

The above approach works when calling an action method with simple arguments, e.g. an int.

However, when attempting to call an action method like this:

public IQueryable<Order> GetAllOrdersForUser(User user)

In which "User" is itself a domain object, not a simple object like an int, how does one pass the user? Every attempt I have made to make this call -- and I have made a great many -- yields a 404 or some other kind of server error. The method works fine from the MVC app.

Since the action method returns IQueryable, it should be callable via GET, per RO documentation. I've tried a great variety of approaches, and read through the RO spec, but unless I am missing something it seems silent on this. I've tried GET and PUT, even though I think it should be a GET. I've tried passing an encoded, as well as a non-encoded, URL for the User object in question. I've tried passing the id for the User. I've tried passing the parameter in the query string in each of these ways, as well as in the body of a POST. I've set the content-type in the header to various things. I've looked at the RO source code, but it frankly looks like a major investment of time to understand that. I would be very grateful for any assistance on this. Thank you.

Read the full discussion online.

To add a post to this discussion, reply to this email (restfulobjects@discussions.codeplex.com)

To start a new discussion for this project, email restfulobjects@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com

Aug 18, 2012 at 8:14 AM

Thank you for this prompt response. I have read and now re-read sections 2.9, and 2.10 of the RO spec. including 2.9.2, Format Arguments. I'm not aware of anything I'm doing that's inconsistent with it, though I imagine, something is. I url encoded each of these:

{"value": {"href": "http://foobar.com/MyRestAPI/objects/MyModel.Model.User/1"}

{"user": {"value": {"href": "http://foobar.com/MyRestAPI/objects/MyModel.Model.User/1"}}

{"value": {"ref": "http://foobar.com/MyRestAPI/objects/MyModel.Model.User/1"}

{"user": {"value": {"ref": "http://foobar.com/MyRestAPI/objects/MyModel.Model.User/1"}}

Oddly, in the RO 1.0.0 spec in section 2.10, Passing arguments to resources, "ref" is used instead of "href". So I tried with both. There's a trailing comma in the JSON on that page, so I tried some trailing commas. For each, I then issued a GET request resembling the following:

http://foobar.com/MyRestAPI/services/MyModel.Services.OrderRepository/actions/GetAllOrdersForUser/invoke?%7B%22value%22%3A%20%7B%22href%22%3A%20%22http%3A%2F%2Ffoobar.com%2FMyRestAPI%2Fobjects%2FMyModel.Model.User%2F1%22%7D%0A

I tried leaving out the invoke as well. Every one of these generates error 400 for me. I am able to exercise the underlying model code via the MVC app and that works just fine. So I am puzzled.

Coordinator
Aug 18, 2012 at 9:22 AM

I think that RO spec 2.10 is quite clear on how this should work  -  though I suspect that the missing 'invoke' in the example URL is just a typo in the spec, and the errant comma also.

I also just checked our test suite for RO .NET and we are definitely testing a the invocation of a GET method (which yours is) using both value and reference parameters.

But I have a very vague suspicion of what the issue is here, which you could validate. The particular test I refer to is an action with two params:  the first is a value, the second a reference.  We are not, I think, testing a GET action that has only a single reference parameter -  as yours does.  I don't know why I think that might be the issue but I do.

I can't investigate until Monday, but if you feel like it you could try temporarily changing the signature of your action to:

public IQueryable<Order> GetAllOrdersForUser(string foo, User user)

then use the pattern as defined in 2.10  (but with the invoke in the URL and using GET).  If that works then my suspicion is correct  -  and we can get this put right on Monday.

Coordinator
Aug 20, 2012 at 8:28 AM
Edited Aug 20, 2012 at 8:28 AM

Well, I'm pleased to report that my hunch was completely wrong!  I did just successfully invoke an action using GET that required a single reference parameter.  (However, I will now add a ticket to add an automated test for this scenario).  It might be useful for you to follow this example, to check that you are doing the same thing exactly.  If this works for you, but your own example still doesn't, then we need to look at that example in more detail.

The example is on our own demo server.  (Please note that this demo server is not 100% reliable, as sometimes we need to take it down).  Here's the action:

http://mvc.nakedobjects.net:1081/RestDemo/services/AdventureWorksModel.ProductRepository/actions/ListProductsBySubCategory

You can see from the details that this is a GET, and that the action has one parameter (subCategory), which requires a reference to a product sub-category object e.g.:

http://mvc.nakedobjects.net:1081/RestDemo/objects/AdventureWorksModel.ProductSubcategory/1

So the argument map using that reference looks like this:

{"subCategory": {"value": {"href":"http://mvc.nakedobjects.net:1081/RestDemo/objects/AdventureWorksModel.ProductSubcategory/1"}}}

which URL encodes to:

%7B%22subCategory%22%3A%20%7B%22value%22%3A%20%7B%22href%22%3A%22http%3A%2F%2Fmvc.nakedobjects.net%3A1081%2FRestDemo%2Fobjects%2FAdventureWorksModel.ProductSubcategory%2F1%22%7D%7D%7D

So the whole URL for the invoke is:

http://mvc.nakedobjects.net:1081/RestDemo/services/AdventureWorksModel.ProductRepository/actions/ListProductsBySubCategory/invoke?%7B%22subCategory%22%3A%20%7B%22value%22%3A%20%7B%22href%22%3A%22http%3A%2F%2Fmvc.nakedobjects.net%3A1081%2FRestDemo%2Fobjects%2FAdventureWorksModel.ProductSubcategory%2F1%22%7D%7D%7D

Which returns me a list of products as expected.  You should be able to follow the links.

One suggestion if your own example still doesn't work.  Have you tried switching the logging on to see where the failure is occurring?

 

 

 

 

Aug 21, 2012 at 3:40 AM

I am pleased to report that approach worked and my code is now running. Thank you, Dan and Richard. 

I had overlooked spec section 2.10 originally, and then, was using a tool (Chrome Postman plug-in) that was causing its own errors (have switched to Advanced REST Client.) Another speed bump was the typo in each of the 4 examples in that particular section.

Spec section 2.3.1 discusses another way to call the method in question which does not depend on URL encoding, like this:

http://foobar.com/MyRestAPI/objects/User/5/actions/GetAllOrdersForUser/invoke

Here, User with id = 5 becomes the parameter to GetAllOrdersForUser. This approach only works for methods that take a single parameter.

Now that I have all the orders coming in, I'm seeing how I will need to make subsequent requests to get details on them, which is conceptually clean but problematic performance-wise, especially as the number of orders potentially grows large. I suppose to get fewer requests, I'll need to provide some new interface that assembles what is needed in a single object rather than a collection.

Is there logging other than the server's general logging of requests that can be switched on?

Coordinator
Aug 22, 2012 at 8:35 AM

"Here, User with id = 5 becomes the parameter to GetAllOrdersForUser. This approach only works for methods that take a single parameter."

Not sure I understand the last point.  There's no reason why User can't have an instance method GetAllOrders that takes params (such as fromDate and toDate), which could be invoked that way  -  and if those params are just simple values then there's typically no need for URL encoding.

"Now that I have all the orders coming in, I'm seeing how I will need to make subsequent requests to get details on them, which is conceptually clean but problematic performance-wise, especially as the number of orders potentially grows large. I suppose to get fewer requests, I'll need to provide some new interface that assembles what is needed in a single object rather than a collection."

A future version of the RO spec will support eager loading of the details of the returned objects.  In the meantime you could assemble a new transient-only object (effectively a ViewModel as discussed in the spec) to hold details.  However, as a general piece of guidance, I recommend avoiding situations where you are returning large numbers of objects at all.  Can you not give the user more precise query methods for getting to a smaller number of objects that they are interested in?  Note:  I assume you've already seen Section 4.12 of the developer manual "How to limit the size of returned collections", which can be done in conjunction with query methods.

"Is there logging other than the server's general logging of requests that can be switched on?"

See section 8.5.1. of the Restful Objects for .NET developer manual.  You can get quite detailed logging of what's going on inside the framework.  It doesn't always help, but it can in some circumstances.

Developer
Aug 23, 2012 at 10:04 PM

On 21 August 2012 22:18, jdubman <notifications@codeplex.com> wrote:

Here, User with id = 5 becomes the parameter to GetAllOrdersForUser. This approach only works for methods that take a single parameter.

I'm surprised by that... there's no reason in the spec for this not to support multiple params.

Richard... is this an implementation issue, do you think?

Now that I have all the orders coming in, I'm seeing how I will need to make subsequent requests to get details on them, which is conceptually clean but problematic performance-wise, especially as the number of orders potentially grows large. I suppose to get fewer requests, I'll need to provide some new interface that assembles what is needed in a single object rather than a collection.

The spec offers two approaches to this, though neither is yet implemented in a framework.

The first (sect 32) is to introduce the idea of (what I called) an AddressableViewModel, basically a larger object - as you describe - but that would be non-persistent rather than persistent. Although the framework can provide representations of non-persistent objects, they have no resource (no self link) and so cannot have actions/behaviour - rather limiting.

The second (sec 34.4) is the support for eager loading using an x-ro-follow-links param. This would allow a client to avoid the N+1 loading problem.

Note: if the objects being referenced are immutable (and the framework knows this and puts in an appropriate Cache-Control header, as per 2.13), then the current "naive" approach actually works fine.


Is there logging other than the server's general logging of requests that can be switched on?

This is a implementation-specific question so I'll leave for Richard to reply.

Dan