I have added OData V4 to my Web Api 2 app.
registered OData route in WebApiConfig register method before default route:
//defining the routes for our OData service
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: GenerateEdmModel());
private static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Media>("Media");
return builder.GetEdmModel();
}
MediaController.cs
[EnableQuery]
public IQueryable<ApiMedia> GetMedia(ODataQueryOptions<Media> query )
{
*querying and returning media*
}
but when I call localhost:80880/odata/media
returned response says:
The resource cannot be found.
Requested URL: /odata/media
calling localhost:80880/odata returns this:
{
"#odata.context":"http://localhost:80880/odata/$metadata","value":[
{
"name":"Media","kind":"EntitySet","url":"Media"
}
]
}
so whats wrong here?
(does it looking for a controller named odata?)
I followed the tutorial on http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/create-an-odata-v4-endpoint
you cant believe but I should type mysite.com/odata/Media not mysite.com/odata/media
You will quickly find this feature. I was looking for 2 days. Uppercase url request depends on the entityset name
builder.EntitySet<Media>("Media"); // if the changed to "media" will work !
Related
So i have two functions that return a customer, which get feeded by two different parameters. One being the ID of the customer and the other being his customer number.
My controller:
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Routing;
using Models;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using System.Web.OData.Extensions;
using Importing;
using Objects;
using Microsoft.OData;
namespace Controllers
{
public class CustomersController : ODataController
{
// GET: CustomerByCNO(5)
[HttpGet]
[ODataRoute("CustomerByCNO({key})")]
[EnableQuery]
public SingleResult<CustomerDTO> GetCustomerByCNO([FromODataUri]string key)
{
Import i = new Import();
var customer = i.GetCustomer(key).ProjectTo<CustomerDTO>().AsQueryable();
return SingleResult.Create(customer);
}
// GET: Customer(5)
[HttpGet]
[ODataRoute("Customer({id})")]
[EnableQuery]
public SingleResult<CustomerDTO> Get([FromODataUri]int id)
{
Import i = new Import();
var customer = i.GetCustomer(id).ProjectTo<CustomerDTO>().AsQueryable();
return SingleResult.Create(customer);
}
}
}
Initialization:
using AutoMapper;
using Models;
using Objects;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using Microsoft.OData.Edm;
namespace API
{
public static class WebApiConfig
{
public static void ConfigureAPI(HttpConfiguration config)
{
config.MapODataServiceRoute(
routeName: "odata",
routePrefix: "",
model: GetEdmModel()
);
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder
{
Namespace = "Controllers",
ContainerName = "DefaultContainer"
};
builder.EntitySet<CustomerDTO>("Customer")
.EntityType.HasKey(c => c.Id)
.CollectionProperty(c => c.CustomFields);
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
}
While the second functions works as intended the first functions does not and the EnsureInitialized() function throws an InvalidOperationException saying, that it is no valid OData path template and that no resource has been found. How can i get this to work? Not quite sure what i am missing here.
UPDATE 1:
Changing the Controller method to this:
[HttpGet]
[ODataRoute("CustomerByNo(No={no})")]
public SingleResult<CustomerDTO> CustomerByNo([FromODataUri] int no)
{
Import i = new Import();
var customer = i.GetCustomer(no.ToString()).ProjectTo<CustomerDTO>().AsQueryable();
return SingleResult.Create(customer);
}
with this additional line in the config:
builder.Function("CustomerByNo").Returns<SingleResult<CustomerDTO>>().Parameter<int>("No");
Made it so i can access the functions at least. I had to change the parameter to an int as well, seems like it doesnt like strings? However the return value is not deserialized and shown as usual. Also if i leave the [EnableQuery] line in the method declaration, the call will crash saying that it doesnt know how to deserialize since it is not bound to the entityset of Customer i guess.
Trying it this way however, leads to the original error message, that the resource could not be found:
builder.EntityType<CustomerDTO>().Collection.Function("CustomerByNo").Returns<SingleResult<CustomerDTO>>().Parameter<int>("No");
You have to declare your custom odata functions in the convention model:
FunctionConfiguration customerByCNOFunction = builder.Function("CustomerByCNO");
customerByCNOFunction.Returns<CustomerDTO>();
customerByCNOFunction.Parameter<string>("key");
Update :
My first answer was for declaring a functions that returns a type not queryable in odata.
To enable query, the function needs to return an odata entity from an entity set :
builder.Function("CustomerByNo").ReturnsFromEntitySet<CustomerDTO>("Customer").Parameter<int>("No")
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
I'm currently developing a Web API and I'm figuring out about how to add a new method inside my controller FilmsController which has to execute a LINQ query simply returning the related JSON to the user. Everything seems correct but when I try to call that API an error 404 appears. The API I'm trying to call is api/NewFilms, which should be correct.
Here is the method GetNewFilms inside FilmsController:
public IQueryable<Film> GetNewFilms()
{
var query = from f in db.Films
orderby f.ReleaseYear descending
select f;
return query;
}
// GET: api/Films
public IQueryable<Film> GetFilms()
{
return db.Films;
}
With the default routing configuration, web api controller allows to have only one GET action (without any parameters). If you have more than one GET actions, you will get a 500 error with message like
Multiple actions were found that match the request
If you need to have more than one GET actions, you may explicitly define a route pattern for those using Attribute routing.
public class FilmsController : ApiController
{
[Route("api/Films/NewFilms")]
public IEnumerable<string> GetNewFilms()
{
return new List<string> { "New Film 1","New Film 1"};
}
// GET: api/Films
public IEnumerable<string> GetFilms()
{
return new List<string> { "Film 1","Film 2"};
}
public string GetFilm(int id)
{
return "A single film";
}
}
Also, you may consider changing your return type from IQueryable to IEnumerable of your Dto ( instead of the entity class created by your ORM)
I get error "The parameter 'wheels' is of Edm type kind 'Collection'.
You cannot call CreateCollectionWriter on a parameter that is not of
Edm type kind 'Collection'."
Below are details of my setup:
Web API 2.2 OData v4 service : I have defined Action in WheelsController class in my service like this:
public async Task<IHttpActionResult> UpdateWheels(ODataActionParameters parameters)
{
object value;
parameters.TryGetValue("carId", out value);
int carId= (int)value;
parameters.TryGetValue("wheels", out value)
IEnumerable<Wheel> wheels = (IEnumerable<Wheel>)value;
// logic goes here....
return OK();
}
In WebApiConfig.cs files, the Action configuration is defined as below:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Car>("Cars");
builder.EntitySet<Wheel>("Wheels");
var action = builder.EntityType<Wheel>().Collection.Action("UpdateWheels");
action.Parameter<int>("carId");
action.CollectionParameter<Wheel>("wheels");
I get success in invoking the above action from RESTClient extenstion in FireFox browser as POST request to URL "http://localhost/Service/Wheels/UpdateWheels" with Request Body as
{"carId":2,
"wheels":[{"Id":1,"Name":"Wheel Front 1","Description":"Front wheel left", "PositionEnum":"FrontLeft"},
{"Id":2,"Name":"Wheel Front 2","Description":"Front wheel right", "PositionEnum":"FrontRight"}]
}
However, it gives error when I try to invoke the above service action using Simple.OData.Client in client application such as
public async void TestUpdateWheels(List<Wheel> wheelList)
{
// client is derived from ODataClient from assembly Simple.OData.Client.Core.dll, v4.3.0.0
await client.For<Wheel>()
.Action("UpdateWheels")
.Set(new { carId = 2, wheels = wheelList})
.ExecuteAsync();
}
Error message: The parameter 'wheels' is of Edm type kind
'Collection'. You cannot call CreateCollectionWriter on a parameter
that is not of Edm type kind 'Collection'.
How can I call successfully the above Action from ODataClient?
This turn out to be a bug in Simple.OData.Client version 4.3.0 when I reported to the project site. For details, visit the link https://github.com/object/Simple.OData.Client/issues/117
The new bug fix version 4.7.2 of Simple.OData.Client has fixed the
issue for me!
Try out in this way. It works for me in one of my project.
public async Task<string> TestUpdateWheels(List<Wheel> wheelList)
{
string getRules = await client.ExecuteActionAsScalarAsync<string>
("UpdateWheels", new Dictionary<string, object>
{
{ "YourParamater", wheelList}
});
return getRules ;
}
My MVC 4 web service has a new use case. I need to pass a list of arguments on the query string to a Web API, e.g.,
http://host/SomeWebApi?arg=x&arg=y
I previously did this simply and easily in my Web Site controller using ICollection<string> arg as a parameter in the controller. That is working now, but it is a Web page, as opposed to an API.
Now I am knocking my head against the wall trying to get a Web API version of the same thing to work. I've made a simple test interface, below, and the collection arg is always null. I've tried List<string> and string[] as types as well. What am I overlooking?
Route register:
config.Routes.MapHttpRoute(
name: "Experiment",
routeTemplate: "Args",
defaults: new
{
controller = "Storage",
action = "GetArgTest",
suite = UrlParameter.Optional,
},
constraints: new
{
httpMethod = new HttpMethodConstraint(new HttpMethod[] { new HttpMethod("GET") })
}
);
Web API controller code:
public string GetArgTest(ICollection<string> suite)
{
if (suite == null)
{
return "suite is NULL";
}
else
{
return "suite is NON-NULL";
}
}
Test query string that results in "suite is NULL":
http://localhost:5101/Args?suite=1&suite=2
I came across this answer ApiController Action Failing to parse array from querystring and discovered that to resolve this you need to put in the FromUriAttribute. So in your example:
public string GetArgTest([FromUri]ICollection<string> suite)
{
}
This makes sense I guess, typically with an API controller you'd expect to be doing more POST and PUT requests for which you'd typically include post data rather than a QueryString.