odata and webapi together in the same controller - asp.net-mvc

I have the ticketController
public class ticketsController : ODataController
{
}
Is it possible to have this:
GET /api/tickets?$skip={record_to_skip}&$top={pageSize}
GET /api/tickets/{id}
PUT /api/tickets/{id}
DELETE /api/request/{id}
First GET method uses odata routing, the second GET, PUT, and POST uses web api routing
Thanks

Yes it is possible but you have to use an OData attribute. Your controller should inherit from ApiController not ODataController and on the method/action you want to query paint it with the OData 'Queryable' attribute, check this out for more details:
https://ovaismehboob.wordpress.com/2014/01/18/adding-queryable-support-to-asp-net-web-api-controller-action-methods/
Also if you search for OData queryable attribute you should get a a load of information back

Related

POST to a related collection in WEB API 2 with OData 4

I want to have a route like:
/Accounts(id)/Orders
where I can execute a POST to create an order. I can't find a way to add this route using OData in WebApi. For GET there is a convention to follow to get related collections, but I am not able to find any convention for posting new entities to a related collection.
Is there a standard way to handle this POST request with Web API 2 and OData 4 ?
Added the following attributes to the method and it worked:
[HttpPost]
[ODataRoute("Accounts({key})/Orders")]
public IHttpActionResult Orders([FromODataUri] string key, OrderDto orderDto)
{
}

OData Web API routing

I have a web API exposing ODATA from a SQL stored proc. I want to use a url like /odata/firmhierarchy(225) to pass 225 into a param for the stored proc. It just tells me that it can't find a matching resource. It hits the controller, just skips the method. Thoughts?
In webapiconfig
private static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.EntitySet<Employee>("FirmHierarchy");
return builder.GetEdmModel();
}
Context:
public virtual ObjectResult<Employee> sp_EmployeeHierarchy(Nullable<int> managerEmpID)
{
var managerEmpIDParameter = managerEmpID.HasValue ?
new SqlParameter("ManagerEmpID", managerEmpID) :
new SqlParameter("ManagerEmpID", 0);
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<Employee>("sp_EmployeeHierarchy #ManagerEmpID", managerEmpIDParameter);
}
Only method in controller:
[Queryable]
public IQueryable<Employee> GetFirmHierarchy()
{
return db.sp_EmployeeHierarchy(225).AsQueryable();
//return SingleResult.Create(db.Employees.Where(employee => employee.EmpId == key));
}
This should work:
1.Write another method in your controller:
[EnableQuery]
public IQueryable<Employee> Get([FromODataUri] int key)
{
return db.sp_EmployeeHierarchy(key).AsQueryable();
}
Please note that [EnableQuery] is an attribute introduced in Web API for OData V4. If you are still using Web API for OData V1-3, use [Queryable] still.
2.Then you can send the request
GET /odata/firmhierarchy(225)
and get the employees.
I was able to make ODATA work for a table, when auto-generated from entity framework. However, that generation process didn't want to work for a complex type returned by a Table Valued Function (similar scenario to a SP), because it didn't seem to understand where the key was.
What I found was that I could however make it work. First, I check out this article. He sets things up a bit more manually, where his Get on a companyProcessingController ends up routing for id 3 as "http://localhost:10020/odata/companyProcessing(3)" .
This surprised me. My other generated classes set up the pattern that SomeEntity became SomeEntityController, with methods like GetSomeEntities, and a routing that seemed to me to match the method but dropping the word get. Therefore, dropping the entity name from the Get method name seemed different, but it worked. Proving that the path is actually matching the controller name, not the method name.
In this Case you configure the routing using the data type you're querying for, and the beginning of the controller name. Then the actual path utilizes the beginning of the controller name as well.
And then all of this just brings us essentially to the other posted solution, assuming your controller name is firmhierarchyController
So, now, making sense of this... Try going to http://localhost:55063/odata/$metadata , where your port may differ. You'll notice that ODATA exposes a DataType, which is accessed via a DataSet. When a client tries to query into ODATA, they are trying to query against the DataSet, getting items of the DataType.
The DataSet matching the controller name (less Controller), and the Get methods can indeed just be Get without further extension of the name - and otherwise in this scenario was giving me problems.

Web Api Help Page, multiple Get same parameter break help generator

I have created an OData Controller that exposes three different GET methods.
// http://odata/Companies
IQueryable<Company> Get()
// http://odata/Companies(1)
SingleResult<Company> Get([FromODataUri] key)
// http://odata/Companies(1)/Persons
IQueryable<Persons> GetPersons([FromODataUri] key)
Using this approach the OData Links convention works perfectly but the Help Generator creates only 1 method, the Get(), while it skips Get{key} and GetPersons{key}
If I change the methods in this way:
// http://odata/Companies
IQueryable<Company> Get()
// http://odata/Companies(1)
SingleResult<Company> Get([FromODataUri] key)
// http://odata/Companies(1)/Persons
IQueryable<Persons> GetPersons([FromODataUri] companyKey)
the help is generated but Web Api OData doesn't work anymore cause the framework is looking for a "key" parameter.
If you are using OData v4(System.Web.OData.dll instead of System.Web.Http.OData.dll), you can resolve this issue through attribute routing:
[ODataRoute("Companies({companyKey})/Persons")]
IQueryable<Persons> GetPersons([FromODataUri] companyKey)
For more info about attribute routing, you can reference https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/ODataAttributeRoutingSample/.

OData Web API Patch Request with DTO

Rather than expose my Database Model I'm trying to get my WebAPI endpoints working with DTO equivalents. In my WebAPIConfig referenced from the Global.asax file I have:
builder.EntitySet<ProductDTO>("Products");
builder.EntitySet<Product>("ProductsDB");
Where Product has an extra field that ProductDTO does not have.
My patch request is currently:
[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<ProductDTO> patch)
{
I can see that essentially I need to convert my Delta<ProductDTO> into a Delta<Product> but I cannot see any methods in the Delta class that would allow me to set its underlying EntityType instance.
Does anyone have any advice?
This question is very similar to yours, please reference it:
Microsoft Odata api through a ViewModel has problems in PATCH

Order property in System.Web.Http.AuthorizeAttribute

If I have multiple authorization attributes on an action, my understanding is that for System.Web.Mvc.AuthorizeAttribute I can specify the Order property like:
[CustomAuth(Order=2)]
[CustomAuth(Order=1)]
public ActionResult Get() { }
But this doesn't exist in the authorize attribute in the Web API. How do I order the execution of the attributes in the Web API?
Also, does the attribute at the class level always take precedence over the attribute that decorates the action?
I can answer one of your questions.
Also, does the attribute at the class level always take precedence
over the attribute that decorates the action?
ApiController.ExecuteAsync() runs the list of filters gotten from HttpActionDescriptor.GetFilterPipeline(). Here is the comment given for GetFilterPipeline().
///Returns the filters for the given configuration and action. The filter
///collection is ordered according to the FilterScope (in order from
///least specific to most specific: First, Global, Controller, Action)
So, the gloabl filters run first, followed by controller level and then action level filters.
As far as your other question on how to order, I don't have a clear answer though. I understand the filters (attributes) are retrieved using Type.GetCustomAttributes(). This method does not guarantee any order but it usually returns in the reverse order. For example, if you have an action method like this,
[CustomAuth(Name="1")]
[CustomAuth(Name="2")]
public HttpResponseMessage Get()
{
}
the filter with Name="2" comes first in the list followed by "1" in the list returned by typeof(YourApiController).GetCustomAttributes(). If I were you, I'll not make any assumptions about this order. I'd much rather have one Authorization filter at the action method level and run the logic in the order I want it.
Anyways, if you add two global authz filters like
config.Filters.Add(new CustomAuth() { Name = "g1" });
config.Filters.Add(new CustomAuth() { Name = "g2" });
and have a controller like
[CustomAuth(Name="c1")]
[CustomAuth(Name="c2")]
public class ValuesController : ApiController
{
[CustomAuth(Name="1")]
[CustomAuth(Name="2")]
public HttpResponseMessage Get()
{
}
}
the filters are run in this order: g1, g2, c2, c1, 2, and 1.

Resources