asp.net mvc源码分析-Action篇 ParameterDescriptor
紧接着上篇asp.net mvc源码分析-Action篇 Filter 中提到了 IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);首先这个方法的目的很明白获取当前Action参数名称和值得一个字典。
protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();
foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors) {
parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
}
return parametersDict;
}
这个代码逻辑很简单吧,有前面的文章我们知道这里的actionDescriptor 是一个ReflectedActionDescriptor实例,我们猜测ParameterDescriptor是Paramete的一个包装类,具体返回应该是它的子类。 的GetParameters方法如下
public override ParameterDescriptor[] GetParameters() {
ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
// need to clone array so that user modifications aren't accidentally stored
return (ParameterDescriptor[])parameters.Clone();
}
private ParameterDescriptor[] LazilyFetchParametersCollection() {
return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
ref _parametersCache /* cacheLocation */,
MethodInfo.GetParameters /* initializer */,
parameterInfo => new ReflectedParameterDescriptor(parameterInfo, this) /* converter */);
}
一看到LazilyFetchOrCreateDescriptors这个名称我们就知道 如果有就直接获取,没有就创建。
public static TDescriptor[] LazilyFetchOrCreateDescriptors<TReflection, TDescriptor>(ref TDescriptor[] cacheLocation, Func<TReflection[]> initializer, Func<TReflection, TDescriptor> converter) {
// did we already calculate this once?
TDescriptor[] existingCache = Interlocked.CompareExchange(ref cacheLocation, null, null);
if (existingCache != null) {
return existingCache;
}
TReflection[] memberInfos = initializer();
TDescriptor[] descriptors = memberInfos.Select(converter).Where(descriptor => descriptor != null).ToArray();
TDescriptor[] updatedCache = Interlocked.CompareExchange(ref cacheLocation, descriptors, null);
return updatedCache ?? descriptors;
}
这里的memberInfos=MethodInfo.GetParameters()获取Actin的所有参数。而converter=new ReflectedParameterDescriptor(parameterInfo, this),ReflectedParameterDescriptor构造函数如下:
public ReflectedParameterDescriptor(ParameterInfo parameterInfo, ActionDescriptor actionDescriptor) {
ParameterInfo = parameterInfo;
_actionDescriptor = actionDescriptor;
_bindingInfo = new ReflectedParameterBindingInfo(parameterInfo);
}
在这个ReflectedParameterDescriptor有个属性需要我们注意一下,那就是DefaultValue
public override object DefaultValue {
get {
object value;
if (ParameterInfoUtil.TryGetDefaultValue(ParameterInfo, out value)) {
return value;
}
else {
return base.DefaultValue;
}
}
}
internal static class ParameterInfoUtil { public static bool TryGetDefaultValue(ParameterInfo parameterInfo, out object value) { // this will get the default value as seen by the VB / C# compilers // if no value was baked in, RawDefaultValue returns DBNull.Value object defaultValue = parameterInfo.DefaultValue; if (defaultValue != DBNull.Value) { value = defaultValue; return true ; } // if the compiler did not bake in a default value, check the [DefaultValue] attribute DefaultValueAttribute[] attrs = (DefaultValueAttribute[])parameterInfo.GetCustomAttributes( typeof (DefaultValueAttribute), false ); if (attrs == null || attrs.Length == 0) { value = default ( object ); return false ; } else { value = attrs[0].Value; return true ; } } } |
这段代码主要意思是先找到对象的parameterInfo.DefaultValue值,如果不是null这设置value=parameterInfo.DefaultValue并返回true,如果没有找到我们就找参数是否有DefaultValueAttribute特性,如果有就返回设置value=attrs[0].Value并返回true,否则value=default(object) 并返回false。一旦返回false,ReflectedParameterDescriptor的DefaultValue就会返回null。从这段带代码我们需要注意在申明默认参数尽量写成 public ActionResult Index(string name="majiang") 而不是 public ActionResult Index([DefaultValue("majiang")]string name)
现在我们再来看看构造函数中的那个ReflectedParameterBindingInfo,在参数绑定过程中并不是所有的参数都需要绑定数据的,有写参数是不需要绑定数据。
ReflectedParameterBindingInfo的主要代码如下:
public ReflectedParameterBindingInfo(ParameterInfo parameterInfo) {
_parameterInfo = parameterInfo;
ReadSettingsFromBindAttribute();
}
private void ReadSettingsFromBindAttribute() {
BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(_parameterInfo, typeof(BindAttribute));
if (attr == null) {
return;
}
_exclude = new ReadOnlyCollection<string>(AuthorizeAttribute.SplitString(attr.Exclude));
_include = new ReadOnlyCollection<string>(AuthorizeAttribute.SplitString(attr.Include));
_prefix = attr.Prefix;
}
ReadSettingsFromBindAttribute方法主要是获取参数的BindAttribute来初始化exclude排除参数,include包含参数。这个类还有一个比较特殊的属性
public override IModelBinder Binder 这个将放到后面来说说。
默认或则一般情况(简单类型)下我们不考虑什么排除参数的情况 获取到的BindAttribute为null。
现在我们已经得到Action的Parameters的一个包装对象集合ParameterDescriptor[]。同一个Action看你多次调用为了彼此不影响所以这里需要把这个ParameterDescriptor[]集合给克隆一份。 (ParameterDescriptor[])parameters.Clone();
紧接下来就是根据parameterDescriptor来获取真正值了,调用GetParameterValue方法。
protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) { // collect all of the necessary binding 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 prefix not 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; } |
这段代码说白了就是通过 binder.BindModel方法来获取值,如果没有找到就返回parameterDescriptor.DefaultValue。有关binder.BindModel这个方法很是复杂,需要IModelBinder、IValueProvider、ModelMetadataProvider这几个东西,所以我们将放到后面来统一讲解。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构