代码之家  ›  专栏  ›  技术社区  ›  nfplee

TryUpdateModel不使用前缀和includeProperties

  •  1
  • nfplee  · 技术社区  · 14 年前

    嗨,我有一个名为用户的实体,有两个名为用户名和角色的属性(这是对另一个名为角色的实体的引用)。我正在尝试从一个已回发的表单更新用户名和roleid。在回发操作中,我有以下代码:

    var user = new User();
    
    TryUpdateModel(user, "User", new string[] { "UserName, Role.RoleID" });
    
    TryUpdateModel(user, new string[] { "User.UserName, User.Role.RoleID" });
    

    但是,这些都不会更新role.roleid属性。如果我尝试以下方法:

    TryUpdateModel(user, "User", new string[] { "UserName, Role" });
    
    TryUpdateModel(user);
    

    RoleID已更新,但RoleName属性也已验证。这就是为什么我尝试更具体地更新哪些属性,但我无法得到任何第一个示例。

    如果有人能帮忙,我将不胜感激。谢谢

    2 回复  |  直到 12 年前
        1
  •  1
  •   nfplee    12 年前

    这里有一个完整的解决方案来处理任何关系。

    首先,在应用程序启动事件中放置以下代码行:

    ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
    

    现在,您需要在应用程序的某个地方添加以下类:

    public class CustomModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    
            if (bindingContext.ModelType.Namespace.EndsWith("Models.Entities") && !bindingContext.ModelType.IsEnum && value != null)
            {
                if (Utilities.IsInteger(value.AttemptedValue))
                {
                    var repository = ServiceLocator.Current.GetInstance(typeof(IRepository<>).MakeGenericType(bindingContext.ModelType));
                    return repository.GetType().InvokeMember("GetByID", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, repository, new object[] { Convert.ToInt32(value.AttemptedValue) });
                }
                else if (value.AttemptedValue == "")
                    return null;
            }
    
            return base.BindModel(controllerContext, bindingContext);
        }
    }
    

    请注意,上述代码可能需要根据您的需要进行修改。它亲切地调用IRepository().GetByID(???)。它将在绑定到models.entities命名空间中且具有整数值的任何实体时工作。

    现在,对于视图来说,还有另外一点工作需要做来修复ASP.NET MVC 2中的错误。默认情况下,selectListItem上的selected属性将被忽略,因此我已经创建了自己的DropDownList,它允许您传入选定的值。

    public static class SelectExtensions
    {
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string selectedValue, string optionLabel)
        {
            return DropDownListFor(helper, expression, selectList, selectedValue, optionLabel, null);
        }
    
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string selectedValue, string optionLabel, object htmlAttributes)
        {
            return DropDownListHelper(helper, ExpressionHelper.GetExpressionText(expression), selectList, selectedValue, optionLabel, new RouteValueDictionary(htmlAttributes));
        }
    
        /// <summary>
        /// This is almost identical to the one in ASP.NET MVC 2 however it removes the default values stuff so that the Selected property of the SelectListItem class actually works
        /// </summary>
        private static MvcHtmlString DropDownListHelper(HtmlHelper helper, string name, IEnumerable<SelectListItem> selectList, string selectedValue, string optionLabel, IDictionary<string, object> htmlAttributes)
        {
            name = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
    
            // Convert each ListItem to an option tag
            var listItemBuilder = new StringBuilder();
    
            // Make optionLabel the first item that gets rendered
            if (optionLabel != null)
                listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }, selectedValue));
    
            // Add the other options
            foreach (var item in selectList)
            {
                listItemBuilder.AppendLine(ListItemToOption(item, selectedValue));
            }
    
            // Now add the select tag
            var tag = new TagBuilder("select") { InnerHtml = listItemBuilder.ToString() };
            tag.MergeAttributes(htmlAttributes);
            tag.MergeAttribute("name", name, true);
            tag.GenerateId(name);
    
            // If there are any errors for a named field, we add the css attribute
            ModelState modelState;
    
            if (helper.ViewData.ModelState.TryGetValue(name, out modelState))
            {
                if (modelState.Errors.Count > 0)
                    tag.AddCssClass(HtmlHelper.ValidationInputCssClassName);
            }
    
            return tag.ToMvcHtmlString(TagRenderMode.Normal);
        }
    
        internal static string ListItemToOption(SelectListItem item, string selectedValue)
        {
            var tag = new TagBuilder("option") { InnerHtml = HttpUtility.HtmlEncode(item.Text) };
    
            if (item.Value != null)
                tag.Attributes["value"] = item.Value;
    
            if ((!string.IsNullOrEmpty(selectedValue) && item.Value == selectedValue) || item.Selected)
                tag.Attributes["selected"] = "selected";
    
            return tag.ToString(TagRenderMode.Normal);
        }
    }
    

    现在在你的视野中,你可以说:

    <%= Html.DropDownListFor(m => m.User.Role, Model.Roles, Model.User.Role != null ? Model.User.Role.RoleID.ToString() : "", "-- Please Select --")%>
    <%= Html.ValidationMessageFor(m => m.User.Role, "*")%>
    

    当您在控制器中调用TryUpdateModel时,Role属性将自动更新,并且您不需要做任何其他工作来连接此属性。虽然最初有很多代码,但我发现这种方法可以长期保存大量的代码。

    希望这有帮助。

        2
  •  0
  •   Haacked    14 年前

    考虑为用户调用两次TryUpdateModel,一次。一次。然后将角色分配给用户。

    var user = new User();
    var role = new Role();
    
    TryUpdateModel(user, new string[] { "UserName" });
    TryUpdateModel(role, new string[] { "RoleID" });
    
    user.Role = role;
    

    看看这样行不行。

    推荐文章