我有一个 ViewModel,它的成员之一是一个复杂的对象。复杂对象有 4 个属性(都是字符串)。我正在尝试创建一个可重用的部分视图,我可以在其中传递复杂的对象并让它使用其属性的 html 帮助器生成 html。这一切都很好。但是,当我提交表单时,模型绑定器不会将值映射回 ViewModel 的成员,因此我在服务器端没有得到任何信息。如何读取用户在复杂对象的 html 帮助程序中输入的值。
视图模型
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
我的复杂模型
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)
...
如何在表单提交时绑定此数据,以便父模型包含从部分视图在 Web 表单上输入的数据?
谢谢
编辑:我发现我需要在前面加上“ComplexModel”。到部分视图(文本框)中的所有控件名称,以便它映射到嵌套对象,但我无法将 ViewModel 类型传递给部分视图来获取额外的层,因为它需要通用才能接受多个 ViewModel类型。我可以用 javascript 重写 name 属性,但这对我来说似乎过于贫民区。我还能怎么做?
编辑2:我可以使用 new { Name="ComplexModel.Name" } 静态设置 name 属性,所以我认为除非有人有更好的方法,否则我正在做生意?
您可以使用将前缀传递给部分
@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 = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.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 助手,为 Html.partial 的构造函数提供了部分视图名称和 ModelType 对象,以及带有 Html 字段前缀的 ViewDataDictionary 对象的实例。
这会导致“主视图”的“xyz url”的 GET 请求,并在其中渲染部分视图,并使用前缀生成的输入元素,例如先前的 Name="Title" 现在在相应的 HTML 元素中变为 Name="MySubType.Title",其余表单输入元素也相同。
当向“xyz url”发出 POST 请求时出现问题,期望填写的表单保存到我的数据库中。但是 MVC Modelbinder 没有将我发布的模型数据与填写的表单值绑定在一起,并且 ModelState 也丢失了。 viewdata 中的模型也变为 null。
最后,我尝试使用 TryUppdateModel 方法以发布形式更新模型数据,该方法采用模型实例和 html 前缀(之前传递到部分视图),并且现在可以看到模型与值绑定,并且模型状态也存在。
请告诉我这种方法是否良好或有点多样化!
我遇到了这个问题和解决方案,但我使用的是 dotnet core。所以我使用了 David Carek 的解决方案并将其转换为 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 Core 中达到相同结果的更好方法,请随时做出反应。