Как обеспечить правильную кодировку в динамических свойствах OData v4 - PullRequest
1 голос
/ 25 марта 2019

Я пишу сервис OData V4 / Web API 2 с последними пакетами OData NuGet.У меня проблема, я думаю, это проблема форматирования или, возможно, проблема конфигурации на сервере, но я новичок в OData и WebAPI, поэтому я, вероятно, ошибаюсь.

Проблема заключается в следующем: если я вызываю ODataСлужба с патчем, в которой неиспользуемая строка, такая как «Mølgaard», содержится как в поле, которое указывает на объявленное свойство, так и в динамическое свойство, я получаю в своем методе patch мой контроллер «Mølgaard» в объявленном свойстве, но вИз динамических свойств я получаю необработанное значение "M \ u00f8lgaard".Ожидаемое поведение заключается в том, чтобы получить «Mølgaard» в обоих случаях, и я нахожу очень странным то, что динамические свойства обрабатываются иначе, чем объявленные POCO-свойства.Я попробовал это с сгенерированным MS ODataClient, привязанным к моему сервису, а также с помощью инструмента Postman, в обоих случаях я использую метод Patch с тем же неправильным значением.

Будучи очень новым для OData, я имеюпопытался добавить новый сериализатор, как описано здесь: http://odata.github.io/WebApi/#06-03-costomize-odata-formatter и оттуда изменения, как описано в ответе здесь: OData WebApi V4 .net - Пользовательская сериализация Я также нашел пример использования Newtonsoft.Json.JsonConvert.Короче говоря, ни один из них не помог, и я предполагаю, что ни один из них на самом деле не предназначен для решения этой проблемы.

Я начал с демонстрационного проекта, который нашел здесь: https://github.com/DevExpress-Examples/XPO_how-to-implement-odata4-service-with-xpo

У меня естьдобавил класс POCO следующим образом:

    public class OOrder : IDynamicProperties
    {
        [System.ComponentModel.DataAnnotations.Key]
        public int ID { get; set; }
        // SomeText is the declared property and its value 
        // is then repeated in DynamicProperties with another name
        public string SomeText { get; set; }
        public IDictionary<string, object> DynamicProperties { get; set; }
    }

    // I do not know if I need this, I am using
    // it in a map function
    public interface IDynamicProperties
    {
        IDictionary<string, object> DynamicProperties { get; set; }
    }

И мой конфиг довольно прост:

    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {
            config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
            ODataModelBuilder modelBuilder = CreateODataModelBuilder();
            ODataBatchHandler batchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
            config.MapODataServiceRoute(
                routeName: "ODataRoute",
                routePrefix: null,
                model: modelBuilder.GetEdmModel(),
                batchHandler: batchHandler);
        }

        static ODataModelBuilder CreateODataModelBuilder()
        {
            ODataModelBuilder builder = new ODataModelBuilder();
            var openOrder = builder.EntityType<OOrder>();
            openOrder.HasKey(p => p.ID);
            openOrder.Property(p => p.SomeText);
            openOrder.HasDynamicProperties(p => p.DynamicProperties);
            builder.EntitySet<OOrder>("OOrders");

            return builder;
        }
    }

И моя функция исправления на моем контроллере выглядит следующим образом:

        [HttpPatch]
        public IHttpActionResult Patch([FromODataUri] int key, Delta<OOrder> order)
        {
            if (!ModelState.IsValid) return BadRequest();

            using (UnitOfWork uow = ConnectionHelper.CreateSession()) {
                OOrder existing = getSingle(key, uow);
                if (existing != null) {
                    Order existingOrder = uow.GetObjectByKey<Order>(key);
                    order.CopyChangedValues(existing);
                    mapOpenWithDynamcPropertiesToPersisted(existing, existingOrder);
                    // Intentionally not storing changes for now
                    //uow.CommitChanges();
                    return Updated(existing);
                }
                else {
                    return NotFound();
                }
            }
        }

        private void mapOpenWithDynamcPropertiesToPersisted<TOpen, TPersisted>(TOpen open, TPersisted persisted) 
            where TPersisted : BaseDocument
            where TOpen: IDynamicProperties  {
            if (open != null && persisted != null && open.DynamicProperties != null && open.DynamicProperties.Any()) {
                XPClassInfo ci = persisted.ClassInfo;
                foreach (string propertyName in open.DynamicProperties.Keys) {
                    var member = ci.FindMember(propertyName);
                    if (member != null) {                            
                        object val = open.DynamicProperties[propertyName];
                        // Here, I have tried to deserialize etc
                        member.SetValue(persisted, val);
                    }
                }
            }
        }

После вызова метода order.CopyChangedValues ​​(существующий) существующий экземпляр содержит правильно закодированное значение в свойстве SomeText, но не в соответствующем свойстве Dynamic.

1 Ответ

1 голос
/ 27 марта 2019

Я нашел какой-то ответ, и это, по-видимому, связано с тем, что я сам не читаю должным образом статьи, которые я упоминаю в этом вопросе.Кажется, что ответом является внедренный десериализатор, который использует конвертер json для преобразования динамических свойств, потому что они, по-видимому, всегда в необработанном формате.Моя конфигурация теперь такова:

        public static void Register(HttpConfiguration config) {
            config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
            ODataBatchHandler batchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
            config.MapODataServiceRoute(
                routeName: "ODataRoute",
                routePrefix: null,
                configureAction: builder => builder.AddService<IEdmModel>(ServiceLifetime.Singleton, sp => CreateODataModel())
                    .AddService<ODataBatchHandler>(ServiceLifetime.Singleton, bb => new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer))
                    .AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefaultWithAttributeRouting("ODataRoute", config))
                    .AddService<Microsoft.AspNet.OData.Formatter.Serialization.ODataSerializerProvider>(ServiceLifetime.Singleton, sp => new MiTestSerializerProvider(sp))
                    .AddService<Microsoft.AspNet.OData.Formatter.Deserialization.ODataDeserializerProvider>(ServiceLifetime.Singleton, sp => new MiDynamicPropertiesDeserializerProvider(sp))
                );
        }

Где десериализатор является важным в этом контексте.Мое начало десериализатора выглядит так (требуется соединение провайдера / реализации):

    public class MiDynamicPropertiesDeserializerProvider : DefaultODataDeserializerProvider
    {
        MiDynamicPropertiesDeserializer _edmSerializer;
        public MiDynamicPropertiesDeserializerProvider(IServiceProvider rootContainer) : base(rootContainer) {
            _edmSerializer = new MiDynamicPropertiesDeserializer(this);
        }

        public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType) {
            switch (edmType.TypeKind()) { // Todo: Do I need more deserializers ?
                case EdmTypeKind.Entity: return _edmSerializer;
                default: return base.GetEdmTypeDeserializer(edmType);
            }
        }
    }

    public class MiDynamicPropertiesDeserializer : ODataResourceDeserializer {
        public MiDynamicPropertiesDeserializer(ODataDeserializerProvider serializerProvider) : base(serializerProvider) { }

        private static Dictionary<Type, Func<object, object>> simpleTypeConverters = new Dictionary<Type, Func<object, object>>() {           
            { typeof(DateTime), d => new DateTimeOffset((DateTime)d)  } // Todo: add converters or is this too simple ?
        };

        public override void ApplyStructuralProperty(object resource, ODataProperty structuralProperty, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) {
            if (structuralProperty != null && structuralProperty.Value is ODataUntypedValue) {
                // Below is a Q&D mapper I am using in my test to represent properties
                var tupl = WebApplication1.Models.RuntimeClassesHelper.GetFieldsAndTypes().Where(t => t.Item1 == structuralProperty.Name).FirstOrDefault();
                if (tupl != null) {
                    ODataUntypedValue untypedValue = structuralProperty.Value as ODataUntypedValue;
                    if (untypedValue != null) {
                        try {
                            object jsonVal = JsonConvert.DeserializeObject(untypedValue.RawValue);
                            Func<object, object> typeConverterFunc;
                            if (jsonVal != null && simpleTypeConverters.TryGetValue(jsonVal.GetType(), out typeConverterFunc))
                            {
                                jsonVal = typeConverterFunc(jsonVal);
                            }
                            structuralProperty.Value = jsonVal;
                        }
                        catch(Exception e) { /* Todo: handle exceptions ? */  }
                    }
                }
            }
            base.ApplyStructuralProperty(resource, structuralProperty, structuredType, readContext);
        }
    }

Спасибо всем, кто потратил на это время, я надеюсь, что кто-то найдет эту информацию полезной.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...