custom OData v4 function always returns 406 - odata

So I'm trying to map this method as an Odata function ...
[HttpGet]
[EnableQuery]
public IHttpActionResult ForBuyerOrganisations([FromUri]int[] orgIds)
{
// this returns IQueryable<TRDetails>
// i tried throwing a ToList on there too to ensure it works and it does
var result = service.ForBuyerOrgs(orgIds);
return Ok(result);
}
I have mapped it like this ...
var forBuyers = Builder.EntityType<TRDetails>().Collection.Function("ForBuyerOrganisations");
forBuyers.ReturnsCollection<TRDetails>();
forBuyers.CollectionParameter<int>("orgIds");
... I also tried this ...
var forBuyers = Builder.EntityType<TRDetails>().Collection.Function("ForBuyerOrganisations");
forBuyers.ReturnsCollectionFromEntitySet<TRDetails>();
forBuyers.CollectionParameter<int>("orgIds");
I can call it with the url:
~/TRDetails/ForBuyerOrganisations?orgIds=1
The Problem:
The request executes my code and returns from this method, then the client gets a 406.
Any Ideas?

Yes. #Ouyang indicates the config error. Besides, as I seem, there are other four errors:
you should use [FromODataUri], not [FromUri] for the parameter.
you should call the function with namespace-qualified.
TRDetails/Namespace.ForBuyerOrganisations(...)
you should set the argument as list, because you config it as collection.
you should call the function using the () for parameter.
So, the following request is sample request:
~/TRDetails/Default.ForBuyerOrganisations(orgIds=[5,4,2,3,1])

If TRDetails is an entityset,
forBuyers.ReturnsCollectionFromEntitySet<TRDetails>("TRDetails");
I think you miss the entityset's name.

Related

Ok() vs Ok(null)

What's the difference between Ok() vs Ok(null)?
The Ok(null) returns status code 204 with no body but header, therefore we have to change our code in this way:
[HttpGet]
public IActionResult GetTest(string test)
{
MyClass result = GetMyClass(test)
if(result == null) return Ok();
return Ok(result);
}
I suggest you use NoContent (HttpStatus 204).
That means the request successfully executed, but didn't return a value or object, and it's an official status code for this purpose
See the following example:
[HttpGet()]
public IActionResult GetTest(string test)
{
var result = GetMyClass(test)
return result != null
? Ok(result)
: NoContent();
}
Hope you'll find this useful.
Official documentation says
Ok() Creates a OkResult object that produces an empty Status200OK
response.
Ok(Object) Creates an OkObjectResult object that produces an
Status200OK response.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.ok?view=aspnetcore-2.1
When you pass null , it will creates an OkObjectResult of null , So you are getting 204 status code.
HTTP status code 204 is a valid "success" result that lets the recipient know that there is no content. This actually seems rather appropriate, but still it may not be supported everywhere.
Either your could 'teach' (or program) your recipient to handle it correctly, or you need to apply your workaround.
If you need this workaround in multiple places then you could use a base class to overload Ok() (or more precisely, redefine it) to always do what you need, like this:
public abstract class MyBaseApiController : ApiController
{
public new IHttpActionResult Ok<T>(T content)
{
if (content == null) return base.Ok();
return base.Ok(content);
}
}

Simple.Odata: how to call a function passing parameters in asp.net mvc request body

The title is a bit misleading, here is my situation: with postman, i can call the following url issuing a post and my function works:
http://localhost/odataservice/odata/Evaluations(9)/CreateEmptyForm
For it to work, i have to send the following in the body portion:
{
"#odata.type": "#Common.Data.Client",
"ClientId": 1
}
My issue arises when i try to replicate this inside my asp.net mvc application. There, im doing
public int CreateRvaForm(int clientId, int evalId, int type)
{
var key = Task.Run(async () =>
{
var data = await
client
.For<Evaluation>(Constants.DataService.PLURAL_EVALUATIONS)
.Key(evalId)
.Function( type==0 ? Constants.DataService.FUNCTION_CREATE_RVA_EMPTY : Constants.DataService.FUNCTION_CREATE_RVA_DUPLICATE)
.Set( new{ClientId=clientId} )
.ExecuteAsScalarAsync<int>();
return data;
}).GetAwaiter().GetResult();
return key;
}
Here, the variable client is the Simple.Odata.Client object.
The error i'm getting is something related to route not found; debug shows me that the library is trying to execute the url
http://localhost/odataservice/odata/Evaluations(9)/CreateEmptyForm(clientId=XX).
I dont have access to modify the odata service.
According to this text ("Executing functions and actions"), you should rather try using Action instead of Function since action is POST-based call while function translates to HTTP GET

Adding new parameter to a web API action method without disturbing the existing contract

I have an action method already written in my web api 2.0 project. I would like to add a new parameter without disturbing the existing contract. What is the best way to do that? Appreciate any best practice hints on this :)
Here's the code sample of what I intend to do:
Existing code:
[Route("{myId}",Name="MyId")]
Public IHttpActionResult Get(String myId)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111
Expecting to do something like the below:
//I want to keep the route name same for backwards compatibility.
[Route("{myId}/{myName}",Name="MyId")]
Public IHttpActionResult Get(String myId,string? myName)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111/John
The Url mentioned above hits the method rightly, but I never get the second parameter (myName) populated with John.
Thanks everyone for any help towards this.
Sree.
In your example you have myName as string? which is not allowed as:
The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable'
A test controller was created to implement you action
[RoutePrefix("webapi")]
public class TestsController : ApiController {
[HttpGet]
[Route("{myId}/{myName}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName) {
//Some code to show the values of the parameters
return Ok(new { myId = myId, myName = myName });
}
}
When tested with webapi/1111/John the following response is returned
{"myId":"1111","myName":"John"}
which does include the value for MyName as John
If backwards uri webapi/1111 is tried, a NotFound response is returned as the template does not match the new action.
To fix this you need to make the myName parameter optional. To learn more about that check
Optional URI Parameters and Default Values
The new route will look like
//NOTICE THE `?` ON THE {myName} TEMPLATE
[Route("{myId}/{myName?}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName = null) {...}
You will notice that myName was made optional in the route {myId}/{myName?} and in the action parameter (string myId, string myName = null)
Now when tested with webapi/1111 the following response is returned
{"myId":"1111","myName":null}
Which would match your expected result for backwards compatibility.
String is a reference type so you don't need to make it nullable, it already is. Remove the '?' and remove the Name from the attribute. What happens then?

Odata $top doesn't work with MongoDb

I've stuck into some weird problem. Here is the code
AccountsController.cs
// GET /api/accounts
[HttpGet]
[Queryable(ResultLimit = 50)]
public IQueryable<AccountDto> Get()
{
return this.service.Get();
}
service here - it's AccountService.cs
public IQueryable<AccountDto> Get()
{
return this.readModel.Get();
}
and readModel is of type AccountsReadModel
public IQueryable<AccountDto> Get()
{
return Database.GetCollection<AccountDto>("Accounts").AsQueryable();
}
Database is MongoDb.Driver.Database
the problem is following:
when I trying to query Get method without any parameters -
localhost/api/accounts - it returns all accounts (as intended)
when I use skip: localhost/api/accounts?$skip=n - it skips n and returns rest items
(as intended too)
but, localhost/api/accounts?$top=1 returns all accounts, instead of one.
How can I handle it?
The problem was in [Queryable(ResultLimit=50)]:
it and $top=1 together produces following expression:
coll.Take(1).Take(50) which returns not 1, but 50 (or all elements in collection, in case if there are less elements than 50).
By the way, Database.GetCollection<A>("A").AsQueryable().Take(1).Take(50) - returns not 1 element again.
This looks like bug in MongoDbDriver
use with $orderby
localhost/api/accounts?$top=1&$orderby=...

MVC ActionMethod not finding passed in Value

I have a Create ActionMethod, something along the lines of:
[AcceptVerbs(HttpVerbs.Post)]
public ActionMethod Create(Journey journey)
{
if (Request.IsAjaxRequest())
{
//Save values
return Json(new { JourneyID = journey.JourneyID } );
}
}
The Journey object that I pass in is from my LINQ2SQL datamodel. I call the above ActionMethod from my Javascript using the JQuery.Post function, like:
var journeyData = {
CustomerID: $('#CustomerID').val(),
JourneyID: $('#JourneyID').val(),
EstimatedTravelTime: $('#EstimatedTravelTime').val(),
RouteName: $('#RouteName').val(),
.....
};
$.post('/Journey/Create',
journeyData,
function(jsonResult) {
//deal with result
},
'json'
);
The issue that I am having is that in the ActionMethod Journey.RouteName is always returning as null but the JSON that I pass back has a value, I check this using
alert(JSON.stringify(journeyData));
and in the resultant JSON object RouteName has a value, e.g. 'Go to work'. Any ideas why this wouldn't be being set in the ActionMethod? All other values that I pass back are being set fine.
Just a try and error suggestion:
First thing i would try is to rename "RouteName" param with somethign different, as "RouteName" is also a property of some MVC-redirect methods..
Have you examined your request JSON object that's going to the server?
Have you tried adding quotes to your string property value?
Like this:
var journeyData = {
CustomerID: $('#CustomerID').val(),
JourneyID: $('#JourneyID').val(),
EstimatedTravelTime: $('#EstimatedTravelTime').val(),
RouteName: '"' + $('#RouteName').val() + '"',
.....
};
There are a couple of things to consider. I'm guessing this is already in place, but your model must have a getter and setter on the RouteName property in order to be properly bound. Another thought is you might not be establishing the strong binding. This is usually done as part of the view's page declaration (e.g. Inherits="System.Web.Mvc.ViewPage") but I'm not sure this is happening prior to your post.

Resources