1 回答

TA貢獻1827條經驗 獲得超8個贊
我查看了 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 關注
- 140 瀏覽
添加回答
舉報