2010-08-09 81 views
1

您好我有一個實體稱爲用戶有兩個屬性稱爲UserName和角色(這是對另一個實體稱爲角色的引用)。我正在嘗試從發佈的表單更新UserName和RoleID。在我的回傳動作,我有以下代碼:TryUpdateModel不能使用前綴和IncludeProperties

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屬性也已驗證。這就是爲什麼我試圖更具體的更新哪些屬性,但我不能得到任何第一個例子的工作。

我很感激,如果有人可以幫忙。謝謝

+0

請張貼您的模型和發佈的數據。 – jfar 2010-08-09 17:02:05

回答

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); 
    } 
} 

現在你的視圖中,你可以說:當你調用TryUpdateModel在你的控制器,你必須做沒有額外的工作,這樣組裝起來

<%= 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, "*")%> 

的角色屬性將自動更新。雖然最初我發現很多代碼,但是從長遠來看,這種方法可以節省大量代碼。

希望這會有所幫助。

0

考慮調用TryUpdateModel兩次,一次爲用戶。一次爲角色。然後將角色分配給用戶。

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

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

user.Role = role; 

看看是否有效。

+0

嗨,感謝您的回答,但我已經想出了一些可以節省我不得不多次致電TryUpdateModel的東西。 – nfplee 2010-09-28 12:40:31