1

嗨,我有一个名为 User 的实体,它有两个名为 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 属性也已验证。这就是为什么我试图更具体地更新哪些属性,但我无法让任何第一个示例工作。

如果有人可以提供帮助,我将不胜感激。谢谢

4

2 回答 2

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 属性将自动更新,您无需做任何额外的工作来连接它。虽然最初有很多代码,但我发现这种方法从长远来看可以节省大量代码。

希望这可以帮助。

于 2010-09-28T12:38:55.333 回答
0

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

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

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

user.Role = role;

看看这是否有效。

于 2010-09-22T07:28:59.503 回答