OData V4 (webapi) patch with NavigationProperty collection breaks deserailization on server - asp.net-web-api

I’m trying to go against a Web API Patch method that accepts a Delta. My entity has a navigational property that is a collection. The domain model is trivial, I have a team (entity) and a collection of members (navigational property).
I’m using HttpClient directly instead of the OData client.
The problem I’m running into is that when I send a patch request with the payload = a Team, the Delta deserialized in my controller is null when I include the navigational property collection in my payload.
Eg (ignore that the missing quotes, this is typed in}:
{ Name: Foo } -> serializes to Delta successfully.
{Name: Foo, Members : [] } -> fails to serialize and Delta is null.
Is this supported? I could not find concrete documentation online about whether you can supply navigational properties as an entire collection on patch (not looking for merge support, just want full replace of the collection.)
I also tried tweaking my client to directly send a Delta payload, but the default JsonMediaTypeFormatter is unable to serialize this into a valid request body (always produces empty Json), and the ODataMediaTypeFormatter throws an exception saying it cannot write an object of type Delta, although I have initialized it with every ODataPayloadKind.
I’m sure I’m either doing something wrong or missing something basic, assuming using Delta + patch with HttpClient is not this challenging.

For OData spec, it says:
11.4.3 Update an Entity
...
The entity MUST NOT contain related entities as inline content. It MAY contain binding information for navigation properties. For single-valued navigation properties this replaces the relationship. For collection-valued navigation properties this adds to the relationship.
That is you can't put the navigation properties in the Patch request content.
From Web API OData, it has these codes and these codes. It means that it doesn't allow to patch navigation property form entity.
However, you can shoot your target by following steps:
Patch the principal entity (team)
Patch the dependent entities (members)
use the $ref to update the link
You can refer to my simple test project.

Related

Api Platform - returning data from external API

I'm currently using API Platform to display some data in Elasticsearch. This works fine, but I now have another feature that I'm looking at.
My application needs to deal with a 3rd-party API that needs to hit an endpoint and return some data.
Within my app, I'd like to be able to hit (/api/logistics/{action} - where action is an endpoint, such as login) and this then hits my app layer and returns data (3rd-party can be re-named)
The API calls to the 3rd party are fine, but I'm unsure how to display the response.
I've seen https://api-platform.com/docs/core/data-providers/ which looks like i can create a custom response.
Do i still need to create an entity/model and configure the #ApiResource() with a controller that uses my Data Provider?
If so, then what do i need to add in my annotation, since I won't have an id identifier
I'm fairly new to API Platform and I've not used the Data Provider functionality before
I will not be storing the data from the 3rd party API, just doing a HTTP call, retrieving the response and hopefully displaying it via Api Platform
Thanks
You are mosly right about the dataprovider. But as the docs page General Design Considerations states, "you have to write a plain old PHP object (POPO) representing the input and output of your endpoint. This is the class that is marked with the #ApiResource annotation. This class doesn't have to be mapped with Doctrine ORM, or any other persistence system."
So no, it does not need to be an Entity, but there must be a class marked with the #ApiResource annotation (but putting it in the Entity folder may help to make the #ApiResource() tag work - or adding the folder of your class in api/config/packages/api_platform.yaml).
For an item "get" endpoint your POPO needs an id. The poperty - or if there is only a getter, the getter - must be marked with the #ApiProperty(identifier=true) tag. Usually the easiest way to make one is by imploding/encoding some strings from the response of the external api call that together are unique for the response and will not change. Your dataprovider will have to explode/decode the id and use the components to make the external api call.
For a "post" operation you need a datapersister instead of a dataprovider. Apip will instatiate and populate your POPO and pass it to the datapersister and from there you can make the call to the external api and return an object as the result. If your object is not the same type of POPO you should specify "output"=TheOutputClass::class or put the operation on the output class and specify "input"=TheInputClass::class (replace TheOutputClass or TheInputClass by the actual class name)
For "put" and "patch" you need both a dataprovider, a datapersister and an id. They can have different input and output classes, see the docs about DTOs.
A collectionoperations with method "get" may seem convenient because you can just pass it any query string but your CollectionDataProvider must return an iterable.

Getting error when expanding navigation property that contains a nested navigation property

I'm building an OData service with Web API. Database access is handled via Entity Framework.
I am mapping my EF entities to another set of classes using AutoMapper. Those mapped classes are what is being exposed via the OData service.
I have a model that looks like this:
Assignment(int AssignmentId, int EmployeeId, Employee Employee)
Employee(int EmployeeId, ICollection<Skill> Skills)
Skill(int SkillId, string SkillName)
My OData endpoint exposes an IQueryable<Assignment>. Everything works fine with simple OData queries ($top, $select, $filter, etc). By default, Assignment.Employee is not returned from the service; I am fine with that.
When I attempt to $expand the Employee, I get this error:
{"error":{"code":"","message":"An error has occurred.","innererror":{"message":"Cannot compare 'member 'Skills' of type '[...].Employee''. Only primitive types, enumeration types and entity types are supported.","type":"System.NotSupportedException"[...]}
[...] are not actually part of the error, just pieces of the message I've removed.
At this point I have not requested Skills, and my expectation was that Employee.Skills would not be expanded because I haven't explicitly requested to expand it. I am unsure what Skills is being compared to that is throwing EF off.
The best I can tell is that OData is applying an additional projection on top of any projections I have applied, and it is that projection that EF is having issues with.
Has anyone had experience/success with using OData/EF to navigate properties that had more than one level of depth to it?
I have tried removing AutoMapper and writing the expressions by hand, but I still run into the same error, so I don't believe AutoMapper is causing any issues here.

How to delete entity and its dependencies programmatically?

My client have asked me to delete a custom entity and its dependencies (which prevents deletion of this entity) programmatically.
I have retrieved the dependencies of the custom entity using RetrieveDependenciesForDeleteRequest. This request is giving me the collection of dependencies, which must be deleted before the deletion of that custom entity.
RetrieveDependenciesForDeleteResponse resp =
(RetrieveDependenciesForDeleteResponse) service.Execute(req);
//A more complete report requires more code
foreach (Entity dependencyEntity in resp.EntityCollection.Entities)
{
service.Delete(d.LogicalName, dependencyEntity.Id);
}
Now the issue I am facing here is that dependencyEntity.Id is always empty GUID, instead of valid GUID.
Can some one help me how to achieve this functionality?
The Entity items in the response merely act as data transport objects. They are not real entities. What you are looking for is the attribute named "dependentcomponentobjectid" (this is a Guid type, not an EntityReference). Attribute "dependentcomponenttype" (OptionSetValue) gives you a clue about the type of the component you are dealing with.
Note that a dependentcomponent can in turn be a required component other components are depending on. (E.g. a workflow depends on a workflow activity, which in turn depends on a plugin assembly.) So, a robust removal tool would need to follow a recursive strategy.

Breeze Web API Round Trip issue

Hi I'm trying to get Breeze to create a metadata store but its failing with the message
NamingConvention for this server property name does not roundtrip properly
I had the same self referencing loop with the out of the box Web API and was able to solve but setting
json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
Any help or solutions would be greatly appreciated.
Thanks
Ok so I solved my own question. My table names all started with lower case, so when I got the round trip error it was because it would look at an entity such as refLookup which was being seen as RefLookup by Breeze but was also referenced an entity as refLookup (no camel case as thats the name of the entity). So I just named all my tables in the database with upper case characters. Hope that helps some one in the future.
The Breeze NamingConvention facility supports property aliasing, not entity type name aliasing. Therefore the spelling of the entity type name must match the server-side type name exactly ... even if you choose the camelCasing NamingConvention or create your own custom NamingConvention plug in.
That's why I'm surprised that you had any difficulty with the table/entity class name casing.
I am unable to reproduce this error. Here is what I tried
Added a 'foo' table to my database
Created a corresponding foo class
Exposed it from the Web API controller as a foos query action method
Queried on the Breeze client for all foos
Breeze client had no trouble returning my (two) foo entities.
Note that I did not try to mess with the NamingConvention on the client. I kept the default ... which is that every client entity property name is the same as its corresponding server property name. As I said, the NamingConvention doesn't do anything with the entity type name and there is no representation in the metadata for a difference between server and client entity type names.
Do you think otherwise? Can you provide a sample?
A strong word of caution: Do NOT change the Json.NET property naming convention. All name aliasing must be done by Breeze on the client.
In general, one should not change a Json.NET configuration setting if the [BreezeController] attribute set that value. The only exception I can think of is null value handling; Breeze tells Json.NET to ignore nulls. I think that is a mistake ... and you can tell Json.NET to send null values if you wish.
I had the same issue when using two managers. I found that if you are using the global
breeze.NamingConvention.{whateverYouUse}.setAsDefault();
it needs to be set prior to creating the manager. Also if you are forcing the json serialization to a specific naming convention on the server the naming convention on the client should match. I am using the breezecontext and as long as its set prior to the manager all works as planned..maybe you don't need the server side setting?

Model Binder of Json.Net not being used when i post an object

To clarify...
I configure my WebApiConfig like so:
config.Formatters.JsonFormatter.SerializerSettings.Binder = new TypeNameSerializationBinder("namespace.{0}, assembly");
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
This should allow me to bind derived classes to base class.
And binder does work when WebApi serializes objects to JSON, and sends them to client, but when I post them back to server, the binder isn't used (BindToType method never gets called), and my objects get bound to base class.
When i serialize/deserialize objects manually with this settings it all works fine.
Any ideas?
I had the same problem when trying to deserialize complex objects with a custom JsonConverters. I needed this because I'm using DbGeometry for storing users locations.
I broke my head on this a couple of days, I really thought I was doing something wrong, because every time I posted an geometry to the Web API, the complex type parameter was set to null. This while JsonConverter was perfectly able to convert the json to an filled object.
My workaround for this is written below. I don't like that I can't just use the parameter as I'm supposed to do. But it works, at last.
[HttpPost]
public MyComplexType SaveMyComplexType()
{
var json = Request.Content.ReadAsStringAsync().Result;
var myComplexType = JsonConvert.DeserializeObject<MyComplexType>(json);
//todo: validation and save to db
return myComplexType;
}
After some research, I found that this is a bug in ASP.NET Web Api. When the url encoded parameters are parsed, it just creates a new JsonSerializer (without passing global settings).
I filed it here
http://aspnetwebstack.codeplex.com/workitem/609

Resources