asp.net mvc源码分析-EditorFor

在我们的mvc日常开发会经常遇到什么LabelFor、EditorFor、Editor等等,这个扩展方法有很多是相似的。这里我们以EditorFor来说说吧,我觉得这个相对要复杂一点。

首先我们来看看EditorFor的定义:

 public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object additionalViewData) {
            return TemplateHelpers.TemplateFor(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.Edit, additionalViewData);
        }

虽然EditorFor有很多定义,但是实际上都是调用 TemplateHelpers.TemplateFor方法。

  internal static MvcHtmlString TemplateFor<TContainer, TValue>(this HtmlHelper<TContainer> html, Expression<Func<TContainer, TValue>> expression,
                                                                      string templateName, string htmlFieldName, DataBoundControlMode mode,
                                                                      object additionalViewData)
{
            return MvcHtmlString.Create(TemplateFor(html, expression, templateName, htmlFieldName, mode, additionalViewData, TemplateHelper));
        }
现在大家应该知道TemplateFor方法的主要参数都有哪些了吧,但是在实际开发中我们的templateName、htmlFieldName、additionalViewData通常都是null,mode是DataBoundControlMode.Edit
我们还是举一个例子来说说吧:

 public class UserInfo
    {
        [StringLength(100, MinimumLength = 10)]
        [Required]
        public string UserName { set; get; }

    }

  @Html.EditorFor(model => model.UserName)

这个代码是不是很简单。

现在我们来看看TemplateFor的实现

      return templateHelper(html,
                                  ModelMetadata.FromLambdaExpression(expression, html.ViewData),
                                  htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
                                  templateName,
                                  mode,
                                  additionalViewData);

首先我们来看看  ModelMetadata.FromLambdaExpression(expression, html.ViewData)这句是如何获取ModelMetadata的,具体实现如下:

 如果我们调用的是Editor那么之力调用就是FromStringExpression方法而不是FromLambdaExpression方法,这2个方法相差不大。我们还是来看看FromLambdaExpression这个方法吧:

看了这张图 那么 return GetMetadataFromProvider(modelAccessor, typeof(TValue), propertyName, containerType);这句代码里面的参数相信大家都应该是到了吧。

propertyName=“UserName” containerType=MvcApp.Controllers.UserInfo,modelAccessor就是创建一个实例,实例的创建是通过 model => model.UserName这句。至于GetMetadataFromProvider这个方法就没什么好讲的了,前面的文章已经讲过了,它实际是创建 了一个DataAnnotationsModelMetadata实例。

至于ExpressionHelper.GetExpressionText(expression)这句说白了默认就是返回一个属性名称,具体实现:

 这里的循环执行两次,第一次是执行

 else if (part.NodeType == ExpressionType.MemberAccess) {
                    MemberExpression memberExpressionPart = (MemberExpression)part;
                    nameParts.Push("." + memberExpressionPart.Member.Name);
                    part = memberExpressionPart.Expression;
                }

第二次执行

    else if (part.NodeType == ExpressionType.Parameter) {
                    // Dev10 Bug #907611
                    // When the expression is parameter based (m => m.Something...), we'll push an empty
                    // string onto the stack and stop evaluating. The extra empty string makes sure that
                    // we don't accidentally cut off too much of m => m.Model.
                    nameParts.Push(String.Empty);
                    part = null;
                }

当然这个方法默认的返回结果这里就是UserName了,它默认就是生成html是的id和name属性的值

现在我们再来看看TemplateHelper方法了

 这个方法其实也很简单,获取当前model的值,以及呈现html的format格式,最后这里从新创建了一个ViewDataDictionary实例 viewData,并且把参数中的additionalViewData也合并到这个viewData中来,把当前的值 (visitedObjectsKey也就是最后呈现给textbox的value)给添加到viewData的VisitedObjects属性中。最 后再调用
  return executeTemplate(html, viewData, templateName, mode, GetViewNames, GetDefaultActions);方法。

那么现在我们应该看看ExecuteTemplate方法了:

 这里的GetActionCache方法很简单就是从当前的context.Items中获取一个字典数据,如果没有就实例化一个然后加入到context.Items中。GetDefaultActions方法也很简单
  internal static Dictionary<string, Func<HtmlHelper, string>> GetDefaultActions(DataBoundControlMode mode) {
            return mode == DataBoundControlMode.ReadOnly ? defaultDisplayActions : defaultEditorActions;
        }

其中GetViewNames就是获取view的名称的一个方法:

 默认返回参数顺序是templateHints中的view,其次就是根据参数 数据类型返回相应的默认view。我这里返回的是String。

那么现在我们回到ExecuteTemplate方法中来,

 string fullViewName = modeViewPath + "/" + viewName;这句就是已经找到我们的view了,如果我们先前actionCache中包含该key就直接执行该view并返回,其次通过 ViewEngines.Engines.FindPartialView来找该view,如果找到则输出该view并返回。否则调用默认的处理方式

   if (defaultActions.TryGetValue(viewName, out defaultAction)) {
                        actionCache[fullViewName] = new ActionCacheCodeItem { Action = defaultAction };
                        return defaultAction(MakeHtmlHelper(html, viewData));
                    }

这里的defaultAction对应则DefaultDisplayTemplates.StringTemplate,因为我的 viewName是String,这里的MakeHtmlHelper方法是根据当前的ViewContext和viewData从新实例化一个 HtmlHelper。

DefaultEditorTemplates.StringTemplate方法非常简单:

  return html.TextBox(String.Empty,
                                html.ViewContext.ViewData.TemplateInfo.FormattedModelValue,
                                CreateHtmlAttributes("text-box single-line")).ToHtmlString();

它里面主要是调用TextBox方法:

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes) {
            return InputHelper(htmlHelper, InputType.Text, null, name, value, (value == null) /* useViewData */, false /* isChecked */, true /* setId */, true /* isExplicitValue */, htmlAttributes);
        }

 

这里的InputHelper是真正生成html字符串的地方。这个方法整体比较好理解,不过要注意这个方法里面有这么一句

            tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

这句就是处理对象上面的那些验证属性,效果如图:

我想大家到这里应该对EditorFor方法有个了解了吧,感觉很是复杂。

posted on   dz45693  阅读(4671)  评论(0编辑  收藏  举报

编辑推荐:
· .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语句:使用策略模式优化代码结构

导航

< 2012年11月 >
28 29 30 31 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 1
2 3 4 5 6 7 8
点击右上角即可分享
微信分享提示