locked
UseJson and IgnoreMissingProperties RRS feed

  • Question

  • I am using the new WCF DataServices 5.1 and trying to convert my client app from Atom to JsonLight but I am having some problems with the datacontext setting IgnoreMissingProperties. This is quite important because I want to avoid a substantional rewrite of my code.

    I have one of many generated client entity object which has a custom property marked with a special attribute. This property is not defined in the EDM. Previously I intercepted the serialization of the entity using the datacontext WritingEntity callback and filtered out the custom property to avoid getting errors when writing data.

    When I try to set the context.Format.UseJson() then I can read data from the server without problems but when posting changes to the server for the client entity object I get the exception below. The strange thing is as far as I can see in the code for ReadUndeclaredProperty it is only supposed to throw an exception if the flag IgnoreUndeclaredValueProperty is not set.

    From ODataJsonLightEntryAndFeedDeserializer

    if (!base.MessageReaderSettings.UndeclaredPropertyBehaviorKinds.HasFlag(ODataUndeclaredPropertyBehaviorKinds.IgnoreUndeclaredValueProperty))
                {
                    throw new ODataException(Microsoft.Data.OData.Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyName, entryState.EntityType.ODataFullName()));
                }

    Here is the exception

    System.Data.Services.Client.DataServiceRequestException: An error occurred while processing this request.
    ---> System.Data.Services.Client.DataServiceClientException: {"odata.error":{"code":"","message":{"lang":"nb-NO","value":"An error occurred while processing this request."},
    "innererror":{"message":"The property 'Name' does not exist on type 'Baze.DataEngine.DataModel.BazeInputAdapter'.
    Make sure to only use property names that are defined by the type.","type":"Microsoft.Data.OData.ODataException","stacktrace":"   
    at Microsoft.Data.OData.JsonLight.ODataJsonLightEntryAndFeedDeserializer.ReadUndeclaredProperty(IODataJsonLightReaderEntryState entryState, String propertyName, Boolean propertyWithValue)\r\n   
    at Microsoft.Data.OData.JsonLight.ODataJsonLightEntryAndFeedDeserializer.ReadEntryPropertyWithValue(IODataJsonLightReaderEntryState entryState, String propertyName)\r\n   
    at Microsoft.Data.OData.JsonLight.ODataJsonLightEntryAndFeedDeserializer.ReadEntryContent(IODataJsonLightReaderEntryState entryState)\r\n   
    at Microsoft.Data.OData.JsonLight.ODataJsonLightReader.ReadAtEntryStartImplementationSynchronously()\r\n   
    at Microsoft.Data.OData.JsonLight.ODataJsonLightReader.ReadAtEntryStartImplementation()\r\n   
    at Microsoft.Data.OData.ODataReaderCore.ReadImplementation()\r\n   
    at Microsoft.Data.OData.ODataReaderCore.ReadSynchronously()\r\n   
    at Microsoft.Data.OData.ODataReaderCore.InterceptException[T](Func`1 action)\r\n   
    at Microsoft.Data.OData.ODataReaderCore.Read()\r\n   
    at System.Data.Services.Serializers.EntityDeserializer.ReadEntry(ODataReader odataReader, SegmentInfo topLevelSegmentInfo)\r\n   
    at System.Data.Services.Serializers.EntityDeserializer.Read(SegmentInfo segmentInfo)\r\n   
    at System.Data.Services.Serializers.ODataMessageReaderDeserializer.Deserialize(SegmentInfo segmentInfo)"}}}
       --- End of inner exception stack trace ---
       at System.Data.Services.Client.SaveResult.HandleResponse()
       at System.Data.Services.Client.BaseSaveResult.EndRequest()
       at System.Data.Services.Client.DataServiceContext.SaveChanges(SaveChangesOptions options)
       at System.Data.Services.Client.DataServiceContext.SaveChanges()
       at Baze.DataEngine.Manager.AdapterForms.ManageNodeForm.btnOK_Click(Object sender, EventArgs e) in C:\Baze.DataEngine\Branches\DataEngine_3.0\Services\DataEngine\Source\Management\Imatis.DataEngine.Manager\AdapterForms\ManageNodeForm.cs:line 79

    Tuesday, November 13, 2012 2:21 PM

Answers

  • Not sure if you found an answer yet but I was able to do the equivalent of WritingEntity with the following in WCF DS 5.5

    _dataContext.Configurations.RequestPipeline.OnEntryEnding(
     (a => {
       List<ODataProperty> properties = a.Entry.Properties.ToList();
       foreach (PropertyInfo property in a.Entity.GetType().GetProperties())
       {
        object[] doNotSerializeAttributes = property.GetCustomAttributes(typeof(DoNotSerializeAttribute), false);
        if (doNotSerializeAttributes.Length > 0)
        {
         ODataProperty odataProperty = properties.Where(item => item.Name == property.Name).FirstOrDefault();
          if (odataProperty != null)
           properties.Remove(odataProperty);
        }
       }
       a.Entry.Properties = properties;
       }));

    Tuesday, June 4, 2013 6:20 PM

All replies

  • Just to clarify my comment above if anyone should be wondering. The custom property is only a wrapper property hence I just want the Json serializer/deserializer to ignore it. If the deserializer just skipped any properties not defined in the EDM I would be very happy.
    • Proposed as answer by wilnor Tuesday, June 4, 2013 6:10 PM
    • Unproposed as answer by wilnor Tuesday, June 4, 2013 6:10 PM
    Tuesday, November 13, 2012 2:24 PM
  • My preliminary hack is to read data using UseJson() and to create my own SaveChangesUsingAtom() method which enforces UseAtom() when trying to save changes.

     public void SaveChangesUsingAtom()
     {
         this.Format.UseAtom();
         this.WritingEntity += CustomWritingEntityMethod;
         SaveChanges();
    }

    Tuesday, November 13, 2012 3:05 PM
  • If your property is only a wrapper property, that means that the server also doesn't send it to the client right? So the client model really doesn't need to know about it. If possible you could mark it internal, then the client will ignore it (both for reading and writing).

    Thanks,


    Vitek Karas [MSFT]

    Tuesday, November 13, 2012 7:04 PM
    Moderator
  • It is correct that the property does not need to get sent to the server since its a redundant property. That is what I am trying to achieve.

    I am aware that only public properties are serialized/deserialized. Your solution would however not be possible as the properties comes from an interface. I have a couple of interfaces like INamedEntity and IHasUniqueIdentifier which are inherited by my client entity classes. Some of the entites then need to implement this separately in order to wrap an existing property. I have generic methods using objects implementing these interfaces. I do not think it unreasonable to expect this to work either.

    The thing is that by using a custom attribute like DoNotSerializeAttribute(as indicated somewhere else on this forum) I can filter away these attributes when writing using Atom. However when using Json there seems to be no way to intercept the serialization or to specify that the property should be ignored except through the property IgnoreMissingProperties which is not working. It would have been very nice if one had offered an attribute for specifying that serialization should be ignored or just made the IgnoreMissingProperties work like expected.

    As I mentioned in the first post I cannot understand why this is not working since by looking at the code for the library class ODataJsonLightEntryAndFeedDeserializer where the exception occurs, would seem to indicate that this should have worked fine. The settings class where this behavior property is specified seems to be passed along fine all the way from the DataContext.


    • Edited by lothar777 Thursday, November 15, 2012 10:51 PM
    Wednesday, November 14, 2012 8:25 AM
  • The IgnoreMissingProperties setting on the client only affects the client reader behavior. It does not affect the server reader. In the SaveChanges scenario the error would happen on the server, since the client sends a property the server doesn't know about. So even though the reader code is the same, the settings applied to it are different.

    Thanks,


    Vitek Karas [MSFT]

    Wednesday, November 14, 2012 10:38 AM
    Moderator
  • That makes sense. What you are saying is that the exception is actually a server side exception and not a client side exception. I was fooled by the stacktrace.

    Now, how do I configure the IgnoreMissingProperties values on the server then, since that would obviously solve my problem. Surely there must be a way of injecting that setting in the server so its applied to the deserializer? You see, in this scenario I control both the client and the server and if I can modify the server settings I would be most delighted.

    On the server side I am using EntityFramework and ObjectContext using a database first approach.
    • Edited by lothar777 Wednesday, November 14, 2012 9:17 PM
    Wednesday, November 14, 2012 9:10 PM
  • There is not setting on the server to allow this. And to certain degree I would advice against that since it would mean the server would silently throw away data. Although I understand that if you control both ends of the service it doesn't matter that much.

    Thanks,


    Vitek Karas [MSFT]

    Thursday, November 15, 2012 11:08 AM
    Moderator
  • I understand your reasoning for not letting us do this, but then as an alternative it would be nice to provide an intercept callback on the client side which would allow the offending property to be removed, or give us an attribute which we could specify on the property that would cause the serialization to ignore this property.
    Thursday, November 15, 2012 2:21 PM
  • Hi,

    This is actually a known request. And I know that there are plans to introduce something like the ReadingEntity/WritingEntity events but with the ability to modify the ODataEntry object. This would be the right place to make such modifications. Unfortunately this feature didn't make it into the 5.1 release. And I'm not sure where it stands in terms of priority for the next release.

    The "IgnoreProperty' attribute (or something like that) is also known, but also didn't make it... same story as the above unfortunately :-(.

    You can vote for it here: http://blogs.msdn.com/b/astoriateam/archive/2010/09/10/what-do-you-want-to-see-added-changed-in-wcf-data-services.aspx

    Thanks,


    Vitek Karas [MSFT]

    Thursday, November 15, 2012 2:44 PM
    Moderator
  • I have voted :). I must say though I am surprised over how low this features has been prioritized. The ability to add custom properties in order to implement various interfaces enables easier manipulation of client objects through the use of generics. My code would be 3x as large without this. Lets hope this makes it into one of the upcoming releases. For now I will stick to writing using atom/xml.

    The fact that Writing/ReadingEntity callbacks are atom specific makes it seem quite strange that something similar wasnt offered when the new json format was introduced. It creates an asymmetry in the code and makes the api feel a bit incoherent.

    Thanks for your feedback, its most appretiated.



    • Edited by lothar777 Thursday, November 15, 2012 10:49 PM
    Thursday, November 15, 2012 10:47 PM
  • I need the exact same feature: ReadingEntity/WritingEntity event with Json format.

    Do you have the link to the feature suggestion page for this feature that you voted (there are too many to look for and the search isn't very helpful)?

    Tuesday, December 4, 2012 5:00 AM
  • Can you provide a sample of the use of UseJson? I can't find any examples of its usage and how to provide the IEdmModel.
    Wednesday, January 9, 2013 2:16 AM
  • Just call the following line once you have created your data service context:

    context.Format.UseJson();

    Wednesday, January 9, 2013 2:24 AM
  • When I call that method, I get the following error: When you call the
    UseJson method without a parameter, you must use the LoadServiceModel
    property to provide a valid IEdmModel instance. I am having trouble
    figuring out how to provide the model (ideally without needing to
    access the metadata since I already have the types client side).

    I am using 5.2.0.0 of Microsoft.Data.Services.
    Client.
    Wednesday, January 9, 2013 2:28 AM
  • Are you using VS2012? May need to re-generate the data service reference (Update Service Reference).
    Wednesday, January 9, 2013 2:36 AM
  • I am trying to use DataServiceContext directly to create a generic IRepository<T> implementation that utilizes OData. I'll try to see what generated code gets created using Add Service Reference.
    Wednesday, January 9, 2013 2:40 AM
  • In VS2012, when I try generating a service reference it seems to do it based on the Microsoft.Data.Services.Client 5.0 class definition. Could you provide a sample autogenerated DataServiceContext class that works properly?
    Wednesday, January 9, 2013 4:34 AM
  • Hi Richard,

    The 5.1 code generator embeds the response from the server's $metadata endpoint as XML and uses Microsoft.Data.Edm.Csdl.EdmxReader to parse it when UseJson is called. You could also just use an arbitrary implementation of IEdmModel, or the constructable EdmModel implementation provided.

    It's important to note that the service's model contains quite a bit of information that is not reflected in the generated client types, but is necessary for interpreting a V3 JSON response.

    If you have the 5.1 update installed (the actual installer, not just NuGet) then you should get this new code whenever you run Add/Update Service Reference against any V3 OData service. It will not be generated for a V2 service. If that's not happening, please let me know.


    Matt Meehan (WCF Data Services / OData)



    Friday, January 25, 2013 5:27 PM
    Moderator
  • I did finally get a V3 service proxy generated and basically ended up get the metadata from the service. Though I am curious what is in the metadata that can not be inferred from the POCO definitions themselves.
    Saturday, January 26, 2013 7:41 PM
  • If you look in the json payload, you will see that it contains only data (no URLs like id, editlink, navigationlink, etc). This is what makes the json payload really clean and compact. The flip side is that the client needs to compute these URLs so that the updates, etc keep working.

    • For id, editlink, etc, the client needs to know the "EntitySet" and the key properties. Ideally, you can argue that the key properties can come from the attributes, but currently we do not have an attribute for EntitySet.
    • For etag, we need to know the etag properties - we currently do not have any attributes for these.
    • For expanded navigation properties, we need to know the set of the associated end so that the client can compute the id and editlink for the nested entities.

    For all of these scenarios, we could have come up with more attributes, but thinking long term, specifying the model gives the ultimate flexibility. As we add more features, we will need more and more of these attributes, and it will become really complicated and confusing for developers to understand what attributes they need or so. Does that make sense?

    Thanks

    Pratik


    This posting is provided "AS IS" with no warranties, and confers no rights.

    Monday, January 28, 2013 5:46 PM
    Moderator
  • Not sure if you found an answer yet but I was able to do the equivalent of WritingEntity with the following in WCF DS 5.5

    _dataContext.Configurations.RequestPipeline.OnEntryEnding(
     (a => {
       List<ODataProperty> properties = a.Entry.Properties.ToList();
       foreach (PropertyInfo property in a.Entity.GetType().GetProperties())
       {
        object[] doNotSerializeAttributes = property.GetCustomAttributes(typeof(DoNotSerializeAttribute), false);
        if (doNotSerializeAttributes.Length > 0)
        {
         ODataProperty odataProperty = properties.Where(item => item.Name == property.Name).FirstOrDefault();
          if (odataProperty != null)
           properties.Remove(odataProperty);
        }
       }
       a.Entry.Properties = properties;
       }));

    Tuesday, June 4, 2013 6:20 PM
  • Does this solution work with OdataClient for V4. I have the exact code setup and hooked up into RequestPipeline OnEntryEnding api and still everything gets sent over to the server and property is not removed.

    Any pointer is appreciated.


    sam

    Friday, March 27, 2015 12:28 AM