1 回答
TA贡献1827条经验 获得超7个赞
我查看了 OData(WebAPI 版本)的源代码并(可能)发现了核心问题。问题也适用于 ASP.NET Core 版本,因为它与 ASP.NET WebAPI 共享代码库。
问题
你调用Patch(TStructuralType original)
方法,方法又调用CopyChangedValues(TStructuralType original)
方法。两者都是班级的公共成员Delta<T>
public void Patch(TStructuralType original){ CopyChangedValues(original); }
内部CopyChangedValues(TStructuralType original)
方法是一段处理将值复制到原始实例的代码。代码迭代PropertyAccessor<TStructuralType>
数组并调用Copy(TStructuralType from, TStructuralType to)
方法。
// For regular non-structural properties at current level.
PropertyAccessor<TStructuralType>[] propertiesToCopy =
this._changedProperties.Select(s => _allProperties[s]).ToArray();
foreach (PropertyAccessor<TStructuralType> propertyToCopy in propertiesToCopy)
{
propertyToCopy.Copy(_instance, original);
}
在里面Copy(TStructuralType from, TStructuralType to)实现PropertyAccessor<TStructuralType>你会发现对abstract的调用SetValue(TStructuralType instance, object value)。
public void Copy(TStructuralType from, TStructuralType to)
{
if (from == null)
{
throw Error.ArgumentNull("from");
}
if (to == null)
{
throw Error.ArgumentNull("to");
}
SetValue(to, GetValue(from));
}
这个方法是通过FastPropertyAccessor<TStructuralType>类来实现的。
public override void SetValue(TStructuralType instance, object value)
{
if (instance == null)
{
throw Error.ArgumentNull("instance");
}
if (_isCollection)
{
DeserializationHelpers.SetCollectionProperty(instance, _property.Name, edmPropertyType: null,
value: value, clearCollection: true);
}
else
{
_setter(instance, value);
}
}
重要的代码行是if (_isCollection). 该布尔标志在构造函数中设置并调用类IsCollection()中的静态方法TypeHelper。
public FastPropertyAccessor(PropertyInfo property)
: base(property)
{
_property = property;
_isCollection = TypeHelper.IsCollection(property.PropertyType);
if (!_isCollection)
{
_setter = PropertyHelper.MakeFastPropertySetter<TStructuralType>(property);
}
_getter = PropertyHelper.MakeFastPropertyGetter(property);
}
在IsCollection(Type clrType)我们遍历调用IsCollection(this Type type, out Type elementType).
public static bool IsCollection(Type clrType)
{
Type elementType;
return TypeHelper.IsCollection(clrType, out elementType);
}
以下是注释后面的重要几行// see if this type should be ignored.(这很奇怪,可能表明有人忘记完成他已经开始的事情),其中仅排除string( ) 。char[]其他数组(包括byte[])会跳到以下代码,该代码会积极评估 byte[](以及任何其他数组类型),因为这些类型正在实现IEnumerable<T>接口。
public static bool IsCollection(Type clrType, out Type elementType)
{
if (clrType == null)
{
throw Error.ArgumentNull("clrType");
}
elementType = clrType;
// see if this type should be ignored.
if (clrType == typeof(string))
{
return false;
}
Type collectionInterface
= clrType.GetInterfaces()
.Union(new[] { clrType })
.FirstOrDefault(
t => TypeHelper.IsGenericType(t)
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (collectionInterface != null)
{
elementType = collectionInterface.GetGenericArguments().Single();
return true;
}
return false;
}
如果我们跳回方法实现,我们最终会在类中SetValue(TEntityType entity, object value)调用。DeserializationHelpers.SetCollectionProperty(entity, _property.Name, edmPropertyType: null, value: value, clearCollection: true);DeserializationHelpers
if (_isCollection)
{
DeserializationHelpers.SetCollectionProperty(instance, _property.Name, edmPropertyType: null,
value: value, clearCollection: true);
}
很明显,此方法的实现非常具有防御性,可以避免在集合值为 时抛出异常null。该方法的第一行是,并且在要执行的代码块之后if (value != null)没有任何块或代码。else我们可以从字面上说,对于每个实现 的类型,空值都会被忽略IEnumerable<T>,因此不会被设置。
internal static void SetCollectionProperty(object resource, string propertyName,
IEdmCollectionTypeReference edmPropertyType, object value, bool clearCollection)
{
if (value != null)
{
IEnumerable collection = value as IEnumerable;
Contract.Assert(collection != null,
"SetCollectionProperty is always passed the result of ODataFeedDeserializer or ODataCollectionDeserializer");
Type resourceType = resource.GetType();
Type propertyType = GetPropertyType(resource, propertyName);
Type elementType;
if (!TypeHelper.IsCollection(propertyType, out elementType))
{
string message = Error.Format(SRResources.PropertyIsNotCollection, propertyType.FullName, propertyName, resourceType.FullName);
throw new SerializationException(message);
}
IEnumerable newCollection;
if (CanSetProperty(resource, propertyName) &&
CollectionDeserializationHelpers.TryCreateInstance(propertyType, edmPropertyType, elementType, out newCollection))
{
// settable collections
collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType);
if (propertyType.IsArray)
{
newCollection = CollectionDeserializationHelpers.ToArray(newCollection, elementType);
}
SetProperty(resource, propertyName, newCollection);
}
else
{
// get-only collections.
newCollection = GetProperty(resource, propertyName) as IEnumerable;
if (newCollection == null)
{
string message = Error.Format(SRResources.CannotAddToNullCollection, propertyName, resourceType.FullName);
throw new SerializationException(message);
}
if (clearCollection)
{
newCollection.Clear(propertyName, resourceType);
}
collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType);
}
}
}
解决方案1
第一个可能的解决方案是创建自定义模型绑定程序并处理用于返回空字节数组并向模型绑定程序添加类null的值。byte[]NullByteArrayModelBinder
免责声明:没有测试过,但应该可以。
public class NullByteArrayModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if(bindingContext.ModelType == typeof(byte[])) {
return base.BindModel(controllerContext, bindingContext) ?? new byte[0];
}
return base.BindModel(controllerContext, bindingContext);
}
}
这种方法有一个缺点。OData 的使用者还需要在现在进行检查的array.Length > 0任何地方处理空数组。array != null
解决方案2
第二个选项是自定义序列化和反序列化。
序列化:从空array到null=>array.Length > 0 ? array : null;
反序列化:从null到空array=>array ?? new byte[0];
希望能帮助到你!
- 1 回答
- 0 关注
- 102 浏览
添加回答
举报