Odata Webapi - how to inject a ODataResourceDeserializer in 7.0 core?

Deadly 提交于 2021-02-07 08:22:35

问题


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:

  1. 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);
        }
    }
    
  2. 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 and OnDeserialized 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);
        }
    }
    
  3. 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 SerializerProvider

    private 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!