1

こんにちは、UserName と Role という 2 つのプロパティを持つ User というエンティティがあります (これは、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 のバグを修正するために必要な作業がもう 1 つあります。デフォルトでは、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, "*")%>

Role プロパティは、コントローラーで TryUpdateModel を呼び出すと自動的に更新されます。これを接続するために追加の作業を行う必要はありません。最初は大量のコードですが、このアプローチは長期的には大量のコードを節約できることがわかりました。

お役に立てれば。

于 2010-09-28T12:38:55.333 に答える
0

TryUpdateModelを2回呼び出すことを検討してください。1回はユーザーに対してです。役割のために一度。次に、ユーザーに役割を割り当てます。

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 に答える