OData / odata.net

ODataLib: Open Data Protocol - .NET Libraries and Frameworks
https://docs.microsoft.com/odata
Other
686 stars 349 forks source link

Client can't deserialize an entity with a non-public-setter property #163

Closed alexis-y closed 8 years ago

alexis-y commented 9 years ago

Overview

The DataServiceContext can't read entities whose classes have a property with a non-public setter. Instead it throws an InvalidOperationException, like 'The closed type [property's type] does not have a corresponding [property name] settable property'.

Having a property with non-public setter is an easy scenario, using EF's designer in the service-side to configure the property's setter with any access other tan 'Public' will also cause the $metadata for the service to include an annotation, causing the DataSvcUtil to generate the property's setter with that access.

The net result is that I can't use properties' access to have sane restrictions server-side without causing errors to clients that use DataSvcUtil or Visual Studio's Add Service Reference tool.

I've read elsewhere that the client can't read complex types with non-public properties. This isn't the case here, as in this case we're dealing with entity types.

Tested with

EntityFramework 5.0.0, 6.1.1 Microsoft.Data.Services.Client 5.6.0, 5.6.4

Setup

Create a web application, add an EF5 o 6 designer model, add an entity, and set a property's setter access to private, protected or internal. Setup a WCF Data Service for that model. The resulting metadata for that entity should look like this:

<EntityType Name="ApplicationInstance">
  <Key>
    <PropertyRef Name="ID" />
  </Key>
  <Property Name="ID" Type="Edm.String" Nullable="false" MaxLength="5" FixedLength="false" Unicode="true" />
  <Property Name="Catalog" Type="Edm.String" Nullable="false" MaxLength="16" FixedLength="false" Unicode="true" p6:StoreGeneratedPattern="Computed" p7:SetterAccess="Internal" xmlns:p7="http://schemas.microsoft.com/ado/2006/04/codegeneration" xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/annotation" />
</EntityType>

Next, use the Add Service Reference (or DataSvcUtil) to create client proxy clases for that service. The generated class has that property's setter with non-public Access, just like in the server. So far so good.

Finally, make any query that produces that entity type.

Dim apps = db.ApplicationInstances.ToArray()

Result

InvalidOperationException "The closed type System.String does not have a corresponding Catalog settable property"

Notice that the message is also misleading, in that "System.String" is the property's type, not the entity.

Expected result

The entities are deserialized correctly with their Catalog property set.

Additional notes

I also tried with an internal setter, then having the client assembly's decorated with

<Assembly: InternalsVisibleTo("Microsoft.Data.Services.Client, PublicKey=[Key obtained thru sn.exe]")>

That produced the same result.

Workaround

Edit the generated DataServiceContext to remove the "SetterAccess" attributes from the private GeneratedEdmModel class. Edit the generated entities to make the properties' setter public.

LaylaLiu commented 9 years ago

@alexis-y, thanks for your detailed information. Is this workaround acceptable to you?

alexis-y commented 9 years ago

It is a workable solution. It has moved me to develop and publish (internally) a client assembly with the fixed classes, and just instruct users of my service to use that instead of the ASR dialog. It's a little more involved, but that's fine.

chuanboz commented 9 years ago

We also meet this issue in Microsoft Intune service, after did some investigation I think the issue is in ClientPropertyAnnotation.cs code around line 103/104 on constructor

https://github.com/chuanboz/odata.net/blob/master/src/Microsoft.OData.Client/Metadata/ClientPropertyAnnotation.cs MethodInfo propertyGetMethod = propertyInfo.GetGetMethod(); MethodInfo propertySetMethod = propertyInfo.GetSetMethod();

the GetGetMethod/GetSetMethod have an override that takes a Boolean true to indicate non-public property.

chuanboz commented 9 years ago

@LaylaLiu , Ping.

LaylaLiu commented 9 years ago

@chuanboz, Sorry to reply it so late. You are right. Your suggestion could solve this issue. Are you using OData V4 or OData V3? If you are using OData V4, Would you like to contribute the fix?

chinadragon0515 commented 8 years ago

The issue on v4 is fixed. And will merge fix on v3. thanks.

chinadragon0515 commented 8 years ago

v3 fix is committed, close the issue.