TryUpdateModel不使用Prefix和IncludeProperties

时间:2010-08-09 15:11:50

标签: c# asp.net asp.net-mvc updatemodel

您好我有一个名为User的实体,其中包含2个名为UserName和Role的属性(它是对另一个名为Role的实体的引用)。我正在尝试从回发的表单更新UserName和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 个答案:

答案 0 :(得分:1)

这是一个完整的解决方案,适用于任何关系。

首先在Application_Start事件中放置以下代码行:

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属性被忽略,所以我想出了我自己的DropDownListFor,它允许你传递选定的值。

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属性将自动更新,您不必再做其他工作来连接它。虽然最初有很多代码,但我发现这种方法可以长期保存大量代码。

希望这有帮助。

答案 1 :(得分:0)

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

var user = new User();
var role = new Role();

TryUpdateModel(user, new string[] { "UserName" });
TryUpdateModel(role, new string[] { "RoleID" });

user.Role = role;

看看是否有效。