odata 4 many-to-many relation ships - odata

Using ODataController In one to many to many or in many-to-many relationship, how can I GET child of child entities.
For example in the OData 4 Sample Service here:
https://github.com/OData/ODataSamples/blob/master/Scenarios/TripPin/src/webapi/ODataSamples.WebApiService/Controllers/PeopleController.cs
I need to know how to implement something like:
public class PeopleController : ODataController
{
...
[ODataRoute("People({key})/Trips/PlanItems)]
public IQuerable<PlanItems> GetPlanItems([FromODataUri] string key])
}
That means I want to retrieve all PlanItems for a Person.
Unfortunately, all methods implement [ODataRoute("People({key})/Trips({tripId})/PlanItems)] only

The URL convention .../People(key)/Trips/PlanItems is not valid as you can see from the error message if you try "The request URI is not valid. Since the segment 'Trips' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource." So the alternative is using URL .../People(key)/Trips?$expand=PlanItems

Related

Odata: Mapping View Model to Domain Model

I have an odata call that works properly the first time but fails during any successive call due to properties not matching. My controller has very little logic as it's just responsible for calling my domain service to query an Entity Framework database and returning the results as view models. However, when I try to sort I get an error because the hierarchy of my view model does not match my domain model.
Domain Model:
public class Parent
{
public object Child
{
public string Name
}
}
View Model:
public class Parent
{
public string ChildName
}
Using the above examples the EF query would return a list of Parents, which I would convert to view models to send off to the client. When a sort request is made on ChildName odata fails because Parent has no concept of ChildName. I need to map ChildName to Child.Name. I've seen examples of people using EntitySets to change the name but none that will map the model in my case.
I ended up using EF to convert my domain model to a view model using a LINQ expression during the query. Example but changed the view model to ParentView and added constructor:
var query = from p in context.Parent select new ParentView(p.Child.Name)
The positive of this is that it's all run on the database so I'm not transporting unnecessary data. Also, I am able to continue to use the odata options in the query since odata will add onto the query. However, I can't help but feel this is not the best way.

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 OData Actions with Entity as parameter

I have a requirement to encapsulate pieces of business logic within a transaction in an OData Web API service. Some of these pieces will need to accept one or more entities.
An example use case might be StockProduct and this might accept a Product entity and a Location entity. It would create the product and update stock records for the Location.
The approach I've taken is to create an unbound OData action that accepts these entities so that both of these can be operated on in a single transaction. Unfortunately neither can Entities be used as an ODataActionParameter nor can they be part of a class and used as a complex parameter.
I can think of a two ways around this:
Create a DTO class that is not an entity that is a mirror of each of my mirror classes and convert from DTO to Model within my action. The problem here is that I already have a DTO for each Model eg. Product.cs and ProductDTO.cs and don't really want to have to create a third class. (Currently, the ProductDTO.cs is used for Posts, Puts, Patches and Deletes and the Product.cs is used for Gets).
Abandon OData actions and create a simple end point that accepts whatever I like. I'm not keen on going down the second route as I'd like to use OData exclusively.
Any thoughts or suggestions?
You can use the ActionConfiguration.EntityParameter() method to bind an entity as a parameter to your OData action method.
Here is an example:
ActionConfiguration validate = ModelBuilder.EntityType<TEntity>()
.Collection.Action("Validate");
validate.Namespace = "Importation";
validate.EntityParameter<TEntity>(typeof(TEntity).Name);
validate.CollectionParameter<string>("UniqueFields");
validate.Returns<ValidationResult>();
However, note that the ModelState will not check against the content of the supplied Entity and will set any missing properties to null and properties exceeding the StringLength(x) annotation in your model will still pass. If you wish to validate the entity itself after, use this bit of code in your action method:
[HttpPost]
public virtual IHttpActionResult Validate(ODataActionParameters parameters)
{
//First we check if the parameters are correct for the entire action method
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
//Then we cast our entity parameter in our entity object and validate
//it through the controller's Validate<TEntity> method
TEntity Entity = (TEntity)parameters[typeof(TEntity).Name];
Validate(Entity, typeof(TEntity).Name);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IEnumerable<string> uniqueFields = parameters["UniqueFields"] as IEnumerable<string>;
bool result = Importer.Validate(Entity, uniqueFields);
return Ok(result);
}
}
As for your StockProductDTO, it seems to me that this is an new Business Entity by itself and should be treated as such.
You can use a batch request to perform multiple operations within a single request. This allows you to use your existing controllers for inserting your two objects.
https://aspnetwebstack.codeplex.com/wikipage?title=Web+API+Request+Batching

How to only return a subset of specific fields from Model in REST APIController using .NET MVC4

I am writing a Web API that clients can make requests to to return data in XML format. I am implementing this in .NET using Enterprise Foundation and MVC4.
I am struggling a bit with how to only return a subset of some fields from my Models in my Controllers.
For arguments sake, lets say I have a Product model that contains attributes "Id", "Name", "Price" and "Actual Cost" (I am using an example from http://www.asp.net/web-api/overview/creating-web-apis/using-web-api-with-entity-framework/using-web-api-with-entity-framework,-part-6)
I need to expose a Web API for clients to query a specific Product to get its name and price, but in this response I don't want to return the "Actual Cost" property (because this is our secret).
Now in the link I provide this is exactly the problem they are attempting to solve by the use of DTO's (they define a DTO called ProductDTO that contains only the subsets I want to return). I have implemented this solution and I am indeed now able to return only the fields I specify in the DTO.
The problem is that the naming used for the returned entity in XML is now ProductDTO rather than Product, i.e. the returned XML is
{"ProductDTO":[{"Id":1,"Name":"Tomato Soup","Price":1.39}, {"Id":3,"Name":"Yo yo","Price":6.99]}
rather than
{"Product":[{"Id":1,"Name":"Tomato Soup","Price":1.39}, {"Id":3,"Name":"Yo yo","Price":6.99]}
That means that all of our clients currently using our API and expects a "Product" to be returned will now get a "ProductDTO" returned, which means that they will have to make changes to their code and which is unacceptable. I need to provide them with a "Product" with only the relevant set of sub-fields as they are currently getting. How do I achieve this? I cannot simply "ignore" a data member as suggested in prevent property from being serialized in web api because I also have some API cases where I indeed DO need to return ALL the attributes and not only a subset.
Just some background: We have an existing API server interface that was written in Ruby on Rails and we are now moving this over to C# and .NET MVC4. We also have a bunch of client applications already interfacing to our existing, older, Ruby on Rails API Server and we don't want clients to make any changes to their code. We are simply moving our API server code over from Ruby on Rails to C#. In Ruby on Rails I was simply able to apply a filter to the XML Serializer when I need to only return a subset of attributes on certain calls.
If you want to continue down the DTO route that you have started, which IMHO is a good idea as it gives you control of what you export without polluting your internal classes with export specific stuff, you can add a DataContract attribute to your ProductDTO class.
[DataContract(Name="Product")]
public class ProductDTO
{
[DataMember]
public int Id {get;set;}
[DataMember]
public string Name {get;set;}
}
The default XML formatter used in Web API is the DataContractSerializer.
You can read more about this here
Suppose you have a class like
public class product
{
public string Name{get; set;}
..
}
and you don't want to appear it in the response you can just use [XMLIgnore] attribute
[XMLIgnore]
public string Name{get; set;}
hopes this helps.

Prevent user from editing related records

I have a Note domain object which belongs to a Document object. Only an owner of a Document can add Notes so in the Document class there is a canUserAccess() method. In my service layer I can call canUserAccess() to ensure a user only adds Notes to Documents they own.
This works well for create but I have hit a problem with my Note edit action. On post, the viewmodel is mapped to a Note object, providing it with a DocumentID. Problem is, a malicious user could send in a DocumentID on which they do have permission and thus edit a Note belonging to a Document they don't. In my service layer I cannot reliably use the supplied DocumentID yet I need to get the related DocumentID in order to verify that the user can edit the Note. This is an example:
public void editNote(Note note)
{
note.Document = documentRepository.Find(note.DocumentID);
if(note.Document.canUserAccess())
}
How do I get around this? It seems I need to avoid passing the DocumentID with the edit viewmodel but how do I hydrate the related Document in the service layer? There is probably a really simple solution to this and I am just tying myself up in circles!
You do this with BindAtrribute for the model or for the action method by adding a white list with the properties you want to be bound :
for the model
[Bind(Include="NoteText,NoteTitle")]
public Model{}
for the action method
public ViewResult Edit([Bind(Include="NoteText,NoteTitle"]Note note)){}
or use a black list for the properties you don't want to bind :
[Bind(Exclude="DocumentID")]
public Model{}
I would personally use white list with the model class. You might find this article interesting. The last section for under-posting is your case.
Then you don't have the documentID passed, but in your action you can do this:
public ViewResult Edit(Note note)
{
Note updateNote = nodesRep.Get(note.noteID);
if(updateNote.Document.canUserAccess())
{
//replace the values of the updateNote
//you can place this line in your base Repository class
context.Entry<Note>(updateNote).CurrentValues.SetValues(note); //I assume EF 4.1
}
}

Resources