あなたをオープンソースに連れて行きます-ASP.NET_MVC(十三)


今日は前編に引き続きModelBindingについて議論します.
接続をスムーズにするために、前編の最後のコードをもう一度貼り付けて、一応コードセグメント1と呼びましょう.
 
       protected virtual objectGetParameterValue(ControllerContext controllerContext, ParameterDescriptorparameterDescriptor)
        {
            // collect all of the necessarybinding properties
            Type parameterType =parameterDescriptor.ParameterType;
            IModelBinder binder =GetModelBinder(parameterDescriptor);
            IValueProvider valueProvider =controllerContext.Controller.ValueProvider;
            string parameterName =parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
            Predicate<string>propertyFilter = GetPropertyFilter(parameterDescriptor);
 
            // finally, call into the binder
            ModelBindingContext bindingContext= new ModelBindingContext()
            {
                FallbackToEmptyPrefix =(parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefixnot specified
                ModelMetadata =ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
                ModelName = parameterName,
                ModelState =controllerContext.Controller.ViewData.ModelState,
                PropertyFilter =propertyFilter,
                ValueProvider = valueProvider
            };
 
            object result =binder.BindModel(controllerContext, bindingContext);
            return result ??parameterDescriptor.DefaultValue;
        }

コードセグメント1
ここで重要なのは、MVCがIModelBinderインタフェースのインスタンスをどのように取得するか、2つ目は、そのインタフェースのBindModelメソッドがRequest関連情報を対応するModelにバインドする方法です.
まず第1点を見て、コードセグメント1でGetModelBinderメソッドの呼び出しを見つけ、F 12を押してその定義(コードセグメント2)に入る.この方法はただ1つの文で、「//look on the parameter itself,then look in the global table」という注釈を組み合わせると、MVCが先に伝わってきたparameterDescriptorをチェックすることがわかります.BindingInfo.Binderが空であるかどうかは、空でなければその属性を直接返し、空であればBinder sを呼び出す.GetBinderメソッドは、グローバルテーブルでIModelBinderを探します.
        private IModelBinderGetModelBinder(ParameterDescriptor parameterDescriptor)
        {
            // look on the parameter itself,then look in the global table
            returnparameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
        }

コードセグメント2
Binders.GetBinderメソッドの定義は、多くのリロードがあり、最終呼び出しのバージョン(コードセグメント3)を直接見つけます.
        private IModelBinder GetBinder(TypemodelType, IModelBinder fallbackBinder)
        {
            // Try to look up a binder for thistype. We use this order of precedence:
            // 1. Binder returned from provider
            // 2. Binder registered in theglobal table
            // 3. Binder attribute defined onthe type
            // 4. Supplied fallback binder
 
            IModelBinder binder =_modelBinderProviders.GetBinder(modelType);
            if (binder != null)
            {
                return binder;
            }
 
            if(_innerDictionary.TryGetValue(modelType, out binder))
            {
                return binder;
            }
 
            // Function is called frequently,so ensure the error delegate is stateless
            binder =ModelBinders.GetBinderFromAttributes(modelType, (Type errorModel) =>
                {
                    throw new InvalidOperationException(
                       String.Format(CultureInfo.CurrentCulture,MvcResources.ModelBinderDictionary_MultipleAttributes, errorModel.FullName));
                });
 
            return binder ?? fallbackBinder;
        }

コードセグメント3
マイクロソフトのオープンソースプロジェクトグループはコード注釈を重視しています.ほら、この方法は最初はかなり整った注釈です.皆さんの英語はかなりのNBですが、私はやはり恥をかいて翻訳しましょう.
このタイプのbinderを探してみます.次の順序で検索します.
1、providerが返すBinder
2、グローバルテーブルに登録したBinder
3、このタイプで定義されたBinder特性
4、ユーザー提供の代替Binder
以下、この4つの実現原理を1つずつ説明する.
1、providerが返すBinder.
次の文を見つけます.
        IModelBinderbinder = _modelBinderProviders.GetBinder(modelType);
ModelBinderProvidersというプライベートメンバーがどのように割り当てられているかを見て、そのクラスModelBinderDictionaryのコンストラクション関数(コードセグメント4)を見つけます.
        public ModelBinderDictionary()
            :this(ModelBinderProviders.BinderProviders)
        {
        }
 
        internal ModelBinderDictionary(ModelBinderProviderCollectionmodelBinderProviders)
        {
            _modelBinderProviders =modelBinderProviders;
        }

コードセグメント4
modelBinderProvidersはModelBinderProvidersを用いることがわかる.BinderProvidersが割り当てたもので、ModelBinderProviders.BinderProviders【1】とは何か.コードセグメント5を見てみましょう.これは私たちが自分のMVCプロジェクトでGlobalです.asaxファイルのApplication_Startメソッドでは、2番目の文(/*で)は、カスタムmModelBinderProviderをModelBinderProvidersに追加します.BinderProviders集合では、この集合が上の「1」の何かです.
protected voidApplication_Start()
{
AreaRegistration.RegisterAllAreas();
 
ModelBinderProviders.BinderProviders.Add(newCustomModelBinderProvider());//*
 
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

コードセグメント5
2、グローバルテーブルに登録されているBinder.
これは比較的簡単で、innerDictionary.TryGetValue(modelType,out binder)』というコードです.innerDictionaryは辞書のオブジェクトで、この辞書でこのタイプのBinderを探すことを意味します.
3、このタイプで定義されたBinder特性.
コードセグメント3でModelBindersを見つける.GetBinderFromAttributesメソッドは,名前からその役割がAttributesからBinderを取得することであると推測できる.次に、その定義(コードセグメント6)に進む.このコードは、まずGetAttributes()によってタイプtypeのすべての特性(Attributes)を取得し、SingleOfTypeDefaultOrErrorメソッドを呼び出してユーザ定義のCustomModelBinderAttributeを取得し、最後にGetBinderメソッドを呼び出してIModelBinderを取得して返す.
       internal static IModelBinder GetBinderFromAttributes(Type type,Action<Type> errorAction)
       {
           AttributeList allAttrs = newAttributeList(TypeDescriptorHelper.Get(type).GetAttributes());
           CustomModelBinderAttributebinder = allAttrs.SingleOfTypeDefaultOrError<Attribute,CustomModelBinderAttribute, Type>(errorAction, type);
           return binder == null ? null : binder.GetBinder();
       }

コードセグメント6
SingleOfTypeDefaultOrErrorメソッドの定義は、IList汎用クラスの拡張メソッドであり、1つのIListセットからタイプがTMatchである要素を検索し、セットに1つの一致しかない場合、その要素を返す役割を果たすコードセグメント7を参照する.一致しない場合は、空を返します.複数の一致がある場合、コールバック依頼errorActionが呼び出されます.
       /// <summary>
       /// Returns a single value in list matching type TMatch if there is onlyone, null if there are none of type TMatch or calls the
       /// errorAction with errorArg1 if there is more than one.
       /// </summary>
       public static TMatch SingleOfTypeDefaultOrError<TInput, TMatch,TArg1>(this IList<TInput> list, Action<TArg1> errorAction, TArg1errorArg1) where TMatch : class
       {
           Contract.Assert(list != null);
           Contract.Assert(errorAction != null);
 
           TMatch result = null;
           for (int i = 0; i < list.Count; i++)
           {
                TMatch typedValue = list[i] asTMatch;
                if (typedValue != null)
                {
                    if (result == null)
                    {
                        result = typedValue;
                    }
                    else
                    {
                        errorAction(errorArg1);
                        return null;
                    }
                }
           }
           return result;
       }

コードセグメント7