问题
documentation is very sparce and all i tried results in the deserializer injected but normal odata url's not working anymore.
https://github.com/OData/WebApi/issues/158 has solutions buut for 5.6.
The final relevant comment is:
@dbenzhuser - In that commit, look at ODataFormatterTests.cs for how inject a custom deserializer/deserializer provider. You can still use a custom DeserializerProvider but it's injected through DI instead of injecting it through ODataMediaTypeFormatters.
which is quite meaningless. I tried the code there, but it breaks, as I said, the URL's.
Right now my Odata setup is simple:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddOData();
\UnitTest\Microsoft.AspNet.OData.Test.Shared\Formatter\ODataFormatterTests.cs
has examples to inject them (like in lines 379-383)
config.MapODataServiceRoute("IgnoredRouteName", null, builder =>
builder.AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => ODataTestUtil.GetEdmModel())
.AddService<ODataSerializerProvider>(ServiceLifetime.Singleton, sp => new CustomSerializerProvider())
.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp =>
ODataRoutingConventions.CreateDefaultWithAttributeRouting("IgnoredRouteName", config)));
but I seem unable to get this working without removing the core odata routing.
Anyone an idea how to use that for the current version without breaking the base functionality?
回答1:
There are three steps if you want to maintain the base functionality:
Your DeserializerProvider implementation should default to the base implementation for all scenarios that your custom Deserializer can't manage. In the following example the custom deserializer only operates on Resources and not Sets:
public class EntityTypeDeserializerProvider : DefaultODataDeserializerProvider { private readonly DataContractDeserializer _dataContractDeserializer; public EntityTypeDeserializerProvider(IServiceProvider rootContainer) : base(rootContainer) { _dataContractDeserializer = new DataContractDeserializer(this); } public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType) { if(edmType.Definition.TypeKind == EdmTypeKind.Complex || edmType.Definition.TypeKind == EdmTypeKind.Entity) return _dataContractDeserializer; else return base.GetEdmTypeDeserializer(edmType); } }
As with the provider your custom _Deserializer should call through to the base implementation for everything that you do not need to customize. In the following implementation we are only trying to enforce the Order of the properties that are deserialized as well as calling the
DataContract
OnDeserializing
andOnDeserialized
methods, the rest of the deserialization is unaffected:/// <summary> /// OData serializer that oberys the DataMember Order Attribute and OnDeserializing and OnDeserialized attributes on the resource definition /// </summary> public class DataContractDeserializer : ODataResourceDeserializer { public DataContractDeserializer(ODataDeserializerProvider provider) : base(provider) { } public override object CreateResourceInstance(IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) { var resource = base.CreateResourceInstance(structuredType, readContext); var type = resource.GetType(); if(type.GetCustomAttributesData().Any(x => x.AttributeType == typeof(DataContractAttribute))) { // manually call OnDeserializing var init = type.GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).FirstOrDefault(x => x.GetCustomAttributesData().Any(a => a.AttributeType == typeof(OnDeserializingAttribute))); if(init != null) { init.Invoke(resource, new object[] { new StreamingContext(StreamingContextStates.Remoting, readContext ) }); } } return resource; } public override object ReadResource(ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) { var resource = base.ReadResource(resourceWrapper, structuredType, readContext); var type = resource.GetType(); if (type.GetCustomAttributesData().Any(x => x.AttributeType == typeof(DataContractAttribute))) { // manually call OnDeserialized var final = type.GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).FirstOrDefault(x => x.GetCustomAttributesData().Any(a => a.AttributeType == typeof(OnDeserializedAttribute))); if (final != null) { final.Invoke(resource, new object[] { new StreamingContext(StreamingContextStates.Remoting, readContext) }); } } return resource; } public override void ApplyStructuralProperties(object resource, ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) { var type = resource.GetType(); var memberDescriptors = type.GetProperties().Where(x => x.HasAttribute<DataMemberAttribute>()); if (memberDescriptors.Any()) { var orderedProperties = from p in resourceWrapper.Resource.Properties let clsProperty = memberDescriptors.FirstOrDefault(m => m.Name == p.Name) let memberAtt = (DataMemberAttribute)(clsProperty?.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(DataMemberAttribute))) orderby (memberAtt?.Order).GetValueOrDefault() select p; foreach (var property in orderedProperties) { ApplyStructuralProperty(resource, property, structuredType, readContext); } } else base.ApplyStructuralProperties(resource, resourceWrapper, structuredType, readContext); } }
Finally, You need to replace the default DeserializerProvider registration with your own, the following is an example of an overload to
MapODataServiceRoute
that registers the DeserializerProvider from the previous 2 examples.
I have commented out an example of registering a specific SerializerProviderprivate static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, string routePrefix, IEdmModel model, ODataBatchHandler batchHandler = null, ODataUriResolver uriResolver = null, IList<IODataRoutingConvention> routingConventions = null) { return configuration.MapODataServiceRoute(routeName, routePrefix, builder => builder .AddService(ServiceLifetime.Singleton, sp => model) //.AddService<ODataSerializerProvider>(ServiceLifetime.Singleton, sp => new DefaultODataSerializerProvider(sp)) .AddService<ODataDeserializerProvider>(ServiceLifetime.Singleton, sp => new EntityTypeDeserializerProvider(sp)) .AddService(ServiceLifetime.Singleton, sp => batchHandler ?? new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)) .AddService(ServiceLifetime.Singleton, sp => uriResolver ?? new ODataUriResolver()) .AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => routingConventions ?? ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, configuration) ) ); }
来源:https://stackoverflow.com/questions/51825765/odata-webapi-how-to-inject-a-odataresourcedeserializer-in-7-0-core