Получение значений из вложенного сложного объекта, который передается в частичное представление

У меня есть ViewModel, который имеет сложный объект в качестве одного из его членов. Сложный объект имеет 4 свойства (все строки). Я пытаюсь создать частичное представление многократного использования, где я могу передать сложный объект и заставить его генерировать html с помощью html помощников для его свойств. Это все работает отлично. Однако, когда я отправляю форму, связыватель модели не отображает значения обратно члену ViewModel, поэтому я ничего не получаю на стороне сервера. Как я могу прочитать значения, которые пользователь вводит в помощники HTML для сложного объекта.

ViewModel

public class MyViewModel
{
     public string SomeProperty { get; set; }
     public MyComplexModel ComplexModel { get; set; }
}

MyComplexModel

public class MyComplexModel
{
     public int id { get; set; }
     public string Name { get; set; }
     public string Address { get; set; }
     ....
}

контроллер

public class MyController : Controller
{
     public ActionResult Index()
     {
          MyViewModel model = new MyViewModel();
          model.ComplexModel = new MyComplexModel();
          model.ComplexModel.id = 15;
          return View(model);
     }

     [HttpPost]
     public ActionResult Index(MyViewModel model)
     {
          // model here never has my nested model populated in the partial view
          return View(model);
     }
}

Посмотреть

@using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
     ....
     @Html.Partial("MyPartialView", Model.ComplexModel)
}

Частичный вид

@model my.path.to.namespace.MyComplexModel
@Html.TextBoxFor(m => m.Name)
...

Как связать эти данные при отправке формы, чтобы родительская модель содержала данные, введенные в веб-форму из частичного представления?

Спасибо

РЕДАКТИРОВАТЬ: я понял, что мне нужно добавить "ComplexModel". ко всем именам моего элемента управления в частичном представлении (текстовые поля), чтобы он отображался на вложенный объект, но я не могу передать тип ViewModel частичному представлению, чтобы получить этот дополнительный слой, потому что он должен быть универсальным, чтобы принимать несколько ViewModel типы. Я мог бы просто переписать атрибут name с помощью javascript, но это кажется мне чрезмерным гетто. Как еще я могу это сделать?

РЕДАКТИРОВАТЬ 2: Я могу статически установить атрибут имени с новым { Name="ComplexModel.Name" }, поэтому я думаю, что я в бизнесе, если у кого-то нет лучшего метода?

5 ответов

Решение

Вы можете передать префикс частичному использованию

@Html.Partial("MyPartialView", Model.ComplexModel, 
    new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})

который добавит префикс к вам name приписать так, чтобы <input name="Name" ../> станет <input name="ComplexModel.Name" ../> и правильно связать с typeof MyViewModel на пост назад

редактировать

Чтобы сделать это немного проще, вы можете заключить это в HTML-помощник

public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
  string name = ExpressionHelper.GetExpressionText(expression);
  object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
  var viewData = new ViewDataDictionary(helper.ViewData)
  {
    TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = name }
  };
  return helper.Partial(partialViewName, model, viewData);
}

и использовать его как

@Html.PartialFor(m => m.ComplexModel, "MyPartialView")

Если вы используете помощники тегов, partial помощник тега принимает for атрибут, который делает то, что вы ожидаете.

<partial name="MyPartialView" for="ComplexModel" />

Используя for атрибут, а не типичный model атрибут, приведет к тому, что все поля формы в партиале будут названы с ComplexModel. префикс.

Вы можете попробовать передать ViewModel в партиал.

@model my.path.to.namespace.MyViewModel
@Html.TextBoxFor(m => m.ComplexModel.Name)

редактировать

Вы можете создать базовую модель, вставить туда сложную модель и передать основанную модель частичному.

public class MyViewModel :BaseModel
{
    public string SomeProperty { get; set; }
}

 public class MyViewModel2 :BaseModel
{
    public string SomeProperty2 { get; set; }
}

public class BaseModel
{
    public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
    public int id { get; set; }
    public string Name { get; set; }
    ...
}

Тогда ваш частичный будет как ниже:

@model my.path.to.namespace.BaseModel
@Html.TextBoxFor(m => m.ComplexModel.Name)

Если это неприемлемое решение, возможно, вам придется подумать о переопределении связующего элемента модели. Вы можете прочитать об этом здесь.

Я столкнулся с такой же ситуацией и с помощью таких информативных постов изменил свой частичный код, чтобы иметь префикс для сгенерированных во входных элементах, сгенерированных частичным представлением

Я использовал помощник Html.partial, предоставляющий частичное имя и объект ModelType, а также экземпляр объекта ViewDataDictionary с префиксом поля Html для конструктора Html.partial.

Это приводит к GET-запросу "xyz url" для "Main view" и визуализации частичного представления внутри него с элементами ввода, сгенерированными с префиксом, например, ранее Name="Title" теперь становится Name="MySubType.Title" в соответствующем элементе HTML и таким же для остальные элементы формы ввода.

Проблема возникла, когда POST-запрос был отправлен на "xyz url", ожидая, что заполненная форма будет сохранена в моей базе данных. Но MVC Modelbinder не связывал мои данные модели POST с заполненными значениями формы, а также ModelState также теряется. Модель в viewdata также обнулялась.

Наконец, я попытался обновить данные модели в форме "Публикация", используя метод TryUppdateModel, который принимает экземпляр модели и префикс html, которые ранее были переданы в частичное представление, и теперь можно увидеть, что модель связана со значениями, и состояние модели также присутствует.

Пожалуйста, дайте мне знать, если этот подход хорошо или немного разнообразен!

Я столкнулся с этой проблемой и решением, но использовал ядро ​​dotnet. Поэтому я использовал решение Дэвида Карека и преобразовал его в основной вариант dotnet:

      public static class HtmlHelperExtensions
{
    public static Task<IHtmlContent> PartialForAsync<TModel, TProperty>(
        this IHtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        string partialViewName
    )
    {
        if (htmlHelper == null)
        {
            throw new ArgumentNullException(nameof(htmlHelper));
        }

        if (expression == null)
        {
            throw new ArgumentNullException(nameof(expression));
        }

        if (partialViewName == null)
        {
            throw new ArgumentNullException(nameof(partialViewName));
        }

        string name = htmlHelper.GetExpressionText(expression);
        object model = htmlHelper.GetModelExpression(expression).Model;
        var viewData = new ViewDataDictionary(htmlHelper.ViewData)
        {
            TemplateInfo =
            {
                HtmlFieldPrefix = string.IsNullOrEmpty(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix)
                    ? name
                    : $"{htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix}{(name.StartsWith('[') ? "" : ".")}{name}"
            }
        };

        return htmlHelper.PartialAsync(partialViewName, model, viewData);
    }

    public static string GetExpressionText<TModel, TResult>(
        this IHtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TResult>> expression)
    {
        var expressionProvider = htmlHelper.ViewContext.HttpContext.RequestServices
            .GetService(typeof(ModelExpressionProvider)) as ModelExpressionProvider;

        if (expressionProvider == null)
        {
            throw new InvalidOperationException("Unable to retrieve ModelExpressionProvider from DI");
        }

        return expressionProvider.GetExpressionText(expression);
    }

    public static ModelExpression GetModelExpression<TModel, TResult>(
        this IHtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TResult>> expression)
    {
        var expressionProvider = htmlHelper.ViewContext.HttpContext.RequestServices
            .GetService(typeof(ModelExpressionProvider)) as ModelExpressionProvider;

        if (expressionProvider == null)
        {
            throw new InvalidOperationException("Unable to retrieve ModelExpressionProvider from DI");
        }

        return expressionProvider.CreateModelExpression(htmlHelper.ViewData, expression);
    }
}

Использование также немного адаптируется:

      @await Html.PartialForAsync(m => m.ComplexModel, "MyPartialView")

Я также добавил небольшую адаптацию, в которой нет добавленных.если выражение вашей модели начинается с[уметь работать со списками (например,list[1]).

Если кто-то знает лучший способ достижения того же результата в ядре dotnet, не стесняйтесь реагировать.

Другие вопросы по тегам