C# 如何确保ODataV4动态属性中的正确编码

C# 如何确保ODataV4动态属性中的正确编码,c#,odata,C#,Odata,我正在用最新的ODataNuget包编写ODataV4服务/WebAPI 2。我有一个问题,我认为是服务器上的格式问题或配置问题,但我对OData和WebAPI不熟悉,所以我可能错了 问题在于: 如果我使用补丁调用OData服务,其中一个非us字符串(如“Mølgaard”)同时包含在指向已声明属性的字段和动态属性中,我会在已声明属性中的控制器“Mølgaard”上获得补丁方法,但在动态属性中,我会获得原始值“M\u00f8lgaard”。预期的行为是在两者中都获得“Mølgaard”,我发现非

我正在用最新的ODataNuget包编写ODataV4服务/WebAPI 2。我有一个问题,我认为是服务器上的格式问题或配置问题,但我对OData和WebAPI不熟悉,所以我可能错了

问题在于: 如果我使用补丁调用OData服务,其中一个非us字符串(如“Mølgaard”)同时包含在指向已声明属性的字段和动态属性中,我会在已声明属性中的控制器“Mølgaard”上获得补丁方法,但在动态属性中,我会获得原始值“M\u00f8lgaard”。预期的行为是在两者中都获得“Mølgaard”,我发现非常奇怪的是,动态属性的处理方式似乎与声明的POCO属性不同。 我已经用绑定到我的服务的生成的MS ODataClient和一个名为Postman的工具尝试了这一点,在这两种情况下,我使用相同的错误值进入我的补丁方法

作为OData的新手,我尝试添加一个新的序列化程序,如下所述: 从这里的答案中可以看出: 我还发现了一个使用Newtonsoft.Json.JsonConvert的示例。 简言之,两者都没有帮助,我猜两者实际上都不是为了解决这个问题

我从我在这里找到的一个演示项目开始:

我添加了一个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; }
    }
公共类OOrder:IDynamicProperties
{
[System.ComponentModel.DataAnnotations.Key]
公共int ID{get;set;}
//SomeText是声明的属性及其值
//然后用另一个名称在DynamicProperties中重复
公共字符串SomeText{get;set;}
公共IDictionary动态属性{get;set;}
}
//我不知道我是否需要这个,我正在使用
//它是一个映射函数
公共接口IDynamicProperties
{
IDictionary 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;
        }
    }
公共静态类WebApiConfig{
公共静态无效寄存器(HttpConfiguration配置){
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
ODataModelBuilder modelBuilder=CreateODataModelBuilder();
ODataBatchHandler batchHandler=新的DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
config.MapODataServiceRoute(
routeName:“ODataRoute”,
routePrefix:null,
模型:modelBuilder.GetEdmModel(),
batchHandler:batchHandler);
}
静态ODataModelBuilder CreateODataModelBuilder()
{
ODataModelBuilder=新ODataModelBuilder();
var openOrder=builder.EntityType();
HasKey(p=>p.ID);
属性(p=>p.SomeText);
HasDynamicProperties(p=>p.DynamicProperties);
建筑商实体集(“订单”);
返回生成器;
}
}
控制器上的补丁函数如下所示:

        [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);
                    }
                }
            }
        }
[HttpPatch]
公共IHttpActionResult修补程序([FromODataUri]整数键,增量顺序)
{
如果(!ModelState.IsValid)返回BadRequest();
使用(UnitOfWork uow=ConnectionHelper.CreateSession()){
现有订单=getSingle(键,uow);
if(现有!=null){
Order existingOrder=uow.GetObjectByKey(键);
order.CopyChangedValue(现有);
MapOpenWithDynamicPropertiesToPersisted(现有、现有订单);
//有意暂时不存储更改
//uow.CommitChanges();
报税表更新(现有);
}
否则{
返回NotFound();
}
}
}
私有void MapOpenWithdynamicPropertiesToPersisted(TOpen open,TPersisted persisted)
TPersisted的位置:BaseDocument
其中TOpen:IDynamicProperties{
if(open!=null&&persistend!=null&&open.DynamicProperties!=null&&open.DynamicProperties.Any()){
XPClassInfo ci=persistend.ClassInfo;
foreach(open.DynamicProperties.Keys中的字符串propertyName){
var成员=ci.FindMember(propertyName);
如果(成员!=null){
object val=open.DynamicProperties[propertyName];
//在这里,我尝试反序列化等
成员.SetValue(持久化,val);
}
}
}
}

调用order.CopyChangedValues(existing)后,existing实例在其“SomeText”属性中包含正确编码的值,但在相应的动态属性中不包含该值。

我找到了一些答案,这显然与我没有正确阅读问题中提到的文章有关。 答案似乎是使用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))
                );
        }
公共静态无效寄存器(HttpConfiguration配置){
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
ODataBatchHandler batchHandler=新的DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
config.MapODataServiceRoute(
routeName:“ODataRoute”,
routePrefix:null,
configureAction:builder=>builder.AddService(ServiceLifetime.Singleton,sp=>CreateODataModel())
.AddService(ServiceLifetime.Singleton,bb=>new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer))
.AddService(ServiceLifetime.Singleton,sp=>ODataRoutingConventions.create
    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);
        }
    }