OData, method get with key not found - c#

With the code below, I can hit (using Fiddler):
GetCustomers via GET: odata/Customers
Post(CustomerModel customer) via POST: odata/Customers
Delete via DELETE: odata/Customers(5)
The delete method look like :
public IHttpActionResult Delete([FromODataUri] int key)
{
Console.WriteLine(key);
}
I hit the method and I get the key, no problem.
But I don't hit the get method with the key (no problem with the get method without the key, I get the full list) :
// GET: odata/Customers(5)
public IHttpActionResult GetCustomer([FromODataUri] int key)
{
Console.WriteLine(key);
}
I get this error (Response headers via Fiddler):
HTTP/1.1 404 Not Found
The WebApiConfig is :
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<CustomerModel>("Customers");
builder.EntitySet<EmployeeModel>("Employees");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel());
}
}

The method name needs to be Get to be picked up by the OData routing:
Get([FromODataUri] int key)

By Web API OData convention, it should support the following two rules:
HttpMethodName + entityTypeName
HttpMethodName
Convention #1 has high priority than convention #2.
Based on the conventions, you will get 404-NotFound if you only define the following actions in the controller:
GetCustomer([FromODataUri] int key)
GetCustomers([FromODataUri] int key)
Otherwise, it should work if you define at least one of the following actions in the controller:
GetCustomerModel([FromODataUri] int key)
Get([FromODataUri] int key)
https://learn.microsoft.com/en-gb/odata/webapi/built-in-routing-conventions lists the routing conventions used in Web API OData. Hope it can help you. Thanks.

Related

How do I bind multiple entity sets to one odata controller?

I have a lot of different entities that I want to enable OData for. These entities are categorized into different groups based on their type. Currently, the default is to match the EntitySet with the controller name, but I don't want a controller for every entity type that I'll have. Is there a way I can map multiple EntitySets to one controller.
I've tried having the types I'm interested in implement a common interface and specified that interface as my entity set type. I also tried having two entities within one controller and with their own get requests, but no luck. I also tried defining my own routing class that extends EntitySetRoutingConvention, but haven't gotten that to work.
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyEntity1>("MyEntity1");
builder.EntitySet<MyEntity2>("MyEntity2");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
// Web API routes
config.MapHttpAttributeRoutes();
}
This looks for controllers named MyEntity1Controller and MyEntity2Controller.
What I want is something like:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyEntity1>("Generic");
builder.EntitySet<MyEntity2>("Generic"); // Throws an error since Generic is already registered to MyEntity1
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
// Web API routes
config.MapHttpAttributeRoutes();
}
GenericController.cs
// GET: odata/myentity1
[EnableQuery]
public IQueryable<MyEntity1> GetMyEntity1()
{
return db.MyEntity1.AsQueryable();
}
// GET: odata/myentity2
[EnableQuery]
public IQueryable<myentity2> GetMyEntity2()
{
return db.MyEntity2.AsQueryable();
}
The expected results would be I can go to myurl/Generic/MyEntity1 and that would hit a GET request in my Generic Controller. I should also be able to perform odata operations such as myurl/Generic/MyEntity1?$select=Id.
Add the ODataRoute Attribute
/// MyController.cs
// GET: odata/myentity1
[EnableQuery]
[ODataRoute("myentity1")]
public IQueryable<MyEntity1> GetMyEntity1() => db.MyEntity1.AsQueryable();
// GET: odata/myentity2
[EnableQuery]
[ODataRoute("myentity1")]
public IQueryable<MyEntity2> GetMyEntity2() => db.MyEntity2.AsQueryable();

Can't invoke single query GET in WebApi OData

I have web api with OData controller. Models is
public class UserDto
{
public int UserDtoId {get;set;}
public string Name {get;set;}
}
in controller I have two method
[EnableQuery]
public IQueryable<UserDto> Get();
[EnableQuery]
public SingleResult<UserDto> GetUser([FromODataUri] int key);
OData config is:
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<UserDto>("Users").EntityType.HasKey(e=>e.UserDtoId).Name = "User";
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel());
when I try invoke odata/Users(123), the odata try to invoke first get not a get with a key and return me all record from table. When I comment out first get method there is none GET method for this URI access at all. Where I make a mistake?
I try to make [ODataRoute] its doesnt change nothing.
In your code, your two functions are Get() and GetUser(int key)
They should both be 'Get'
Also not sure if it matters, but for the Get(int) one, if it still doesnt work, try changing SingleResult<UserDto> to just UserDto

How to disable REST convention from interfering with my ApiController

This is my api configuration class:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
}
}
This is my api controller class:
public class RoleController : ApiController
{
// Some action that works fine...
// Another action that works fine...
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
I am calling my actions using POST and they are working fine.
But, when I try to call the Delete action using POST I get the following error:
405 Method Not Allowed
The requested resource does not support http method 'POST'.
Clearly, this is because ApiController enforces REST convention
which expects DELETE verb for Delete action.
Now, how do I disable this REST convention constraints
and write my actions in a classic manner?
You can use the HttpPostAttribute to enforce the Action to accept only POST:
public class RoleController : ApiController
{
[HttpPost]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
You may want to keep the REST conventions while allowing certain clients (like HTML forms) to properly use you actions.
So, you can use a combination of HttpPostAttribute and HttpDeleteAttribute or AcceptVerbsAttribute (which allows multiple verbs) to allow multiple verbs:
public class RoleController : ApiController
{
[HttpPost, HttpDelete]
// OR
[AcceptVerbs("DELETE", "POST")
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
If you don't want magic verbs and magic action names you can use route attributes.
Delete config.Routes.MapHttpRoute and set:
config.MapHttpAttributeRoutes();
Now you have to set the routes manually:
[RoutePrefix("~/Role")]
public class RoleController : ApiController
{
[HttpPost]
[Route("~/Delete")]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
In your case I'd stop using any kind of REST conventions.
Instead of having a Delete method on the Role controller you can have a DeleteRole method and allow POST on it. This way nothing will interfere with what you want to do. Nothing forces you to build a REST api if that's not what you need.
There are several things you could do to still build a nice api.
For example, you could return an IHttpActionResult
your method could look like this:
public class RoleController : ApiController
{
[HttpPost]
public IHttpActionResult DeleteRole([FromBody]int RoleID)
{
if ( RoleID <= 0 )
{
return BadRequest();
}
var deleteResult = RoleBL.Delete(RoleID);
return Ok(deleteResult);
}
}
You still return the same object but it's wrapped inside an object with a proper http code so your code which deals with the result, won't change much.

Calling Methods from one API is done but facing Not found error on second API

I have two Wep APIs. I have done CRUD operation using one eg. Customer. But when I built another Similar Web API and called a method It shows:
{,…} Message: "No HTTP resource was found that matches the request URI
http://localhost:23995/Product/Insert'."
MessageDetail: "No route providing a controller name was found to
match request URI '[[same link as above here]]'"
Here is my JS Calling Method:
$scope.Insert = function () {
$http({
method: 'post',
url: 'http://localhost:23995/Product/Insert',
data: JSON.stringify($scope.Product)
}).then(function (response) {
alert("chec");
});
}
In Product Controller
// Insert
[HttpPost]
[Route("{controller}/Insert")]
public string Insert([FromBody] Product newProd) {
newProd.Insert();
return newProd.DbResponse;
}
In supplier Controller
// Insert
[HttpPost]
[Route("{controller}/Insert")]
public string Insert([FromBody] Product newProd) {
newProd.Insert();
return newProd.DbResponse;
}
Assuming you already have attribute routing enabled.
Attribute Routing in ASP.NET Web API 2
To enable attribute routing, call MapHttpAttributeRoutes during
configuration. This extension method is defined in the
System.Web.Http.HttpConfigurationExtensions class.
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
and assuming that given the route you are getting the error on
http://localhost:23995/Product/Insert
Your product controller should look something like this.
[RoutePrefix("product")]
public class ProductController : ApiController {
// ... other code removed for brevity
// Insert
// eg: POST /product/insert
[HttpPost]
[Route("insert")]
public string Insert([FromBody] Product newProd) {...}
}
and your supplier controller would look very similar
[RoutePrefix("supplier")]
public class SupplierController : ApiController {
// ... other code removed for brevity
// Insert
// eg: POST /supplier/insert
[HttpPost]
[Route("insert")]
public string Insert([FromBody] Product newProd) {...}
}
you calling JavaScript should then be able to properly call the desired methods
Do you have a controller named "ProductController" with a method named Insert?
Looks like that's all that is missing for you.

ASP.NET Odata Web API CRUD Operations Not Working

I have spent a about 2 days setting up my Odata Web API project that was before a simple Asp.net mvc4 project.
But still I am not successful in operating even CRUD Operations.
It says this:
<m:message xml:lang="en-US">
No HTTP resource was found that matches the request URI 'http://localhost:53208/odata/Product'.
</m:message>
<m:innererror>
<m:message>
No action was found on the controller 'Product' that matches the request.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
It means its reaching the Controller but not finding actions or there is some problem in my routing settings.
I have following in my WebApiConfig:
config.Routes.MapODataRoute("OData","odata",GetEdmModel());
My getEdmModel method:
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Product");
builder.EntitySet<OfferedProduct>("OfferedProduct");
IEdmModel model = builder.GetEdmModel();
return model;
}
My controller is like this:
public class ProductController : EntitySetController<Product,int>
{
private OfferAssistantDbContext db = new OfferAssistantDbContext();
List<Product> Products = new OfferAssistantDbContext().Products.ToList();
// GET api/Product
[Queryable(PageSize = 10)]
public override IQueryable<Product> Get()
{
return Products.AsQueryable();
}
// GET api/Product/5
public Product GetProduct([FromODataUri] int id)
{
return Products[id];
}
/// and so on... but for this time lets work only on GET operation
Now when I write this in my browser:
http://localhost:53208/odata/Product
it says the error I showed above..
Please guide me where is the problem?
I believe that your controller needs to inherit from ODataController:
public class ProductController : ODataController

Categories