You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core视图提交仅部分ViewModel回传,求便捷解决方案

问题根因

ASP.NET MVC的模型绑定只会从请求的表单字段、路由数据、查询字符串里取值填充ViewModel,没有提交的字段自然就是默认值null。你之前反射遍历的写法生成的隐藏域name属性不符合绑定规则(正确的name需要体现属性的嵌套层级,比如Mandant.S_MD_FIR,你的写法生成的name是property,所以无法绑定)。

最优解决方案推荐

方案1:自定义HtmlHelper扩展批量生成隐藏域(最适配你的需求)

写个通用的扩展方法,递归处理嵌套类、集合属性,自动生成符合绑定规则的隐藏域,代码如下:

public static class HtmlHelperExtensions
{
    public static IHtmlContent HiddenForModelProperties<TModel, TProperty>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
    {
        var sb = new StringBuilder();
        var modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
        var propertyPath = html.NameFor(expression).ToString();
        
        // 处理值类型、字符串等基础类型属性
        if (modelMetadata.IsComplexType == false || modelMetadata.ModelType == typeof(string))
        {
            sb.AppendLine(html.Hidden(expression).ToString());
            return new HtmlString(sb.ToString());
        }
        
        // 处理集合类型
        if (typeof(IEnumerable).IsAssignableFrom(modelMetadata.ModelType) && modelMetadata.ModelType != typeof(string))
        {
            var collection = modelMetadata.Model as IEnumerable;
            if (collection == null) return new HtmlString(sb.ToString());
            var index = 0;
            foreach (var item in collection)
            {
                var itemType = item.GetType();
                foreach (var prop in itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (prop.GetGetMethod() == null || !prop.CanWrite) continue;
                    var propValue = prop.GetValue(item);
                    var inputName = $"{propertyPath}[{index}].{prop.Name}";
                    sb.AppendLine(html.Hidden(inputName, propValue).ToString());
                }
                index++;
            }
            return new HtmlString(sb.ToString());
        }
        
        // 处理复杂类型
        foreach (var property in modelMetadata.Properties)
        {
            if (property.IsComplexType && property.ModelType != typeof(string))
            {
                // 递归处理嵌套复杂类型
                var parameter = Expression.Parameter(typeof(TModel), "m");
                var propertyAccess = Expression.Property(expression.Body, property.PropertyName);
                var lambda = Expression.Lambda<Func<TModel, object>>(Expression.Convert(propertyAccess, typeof(object)), parameter);
                sb.AppendLine(html.HiddenForModelProperties(lambda).ToString());
            }
            else
            {
                var inputName = $"{propertyPath}.{property.PropertyName}";
                sb.AppendLine(html.Hidden(inputName, property.Model).ToString());
            }
        }
        return new HtmlString(sb.ToString());
    }
}

使用方法非常简单,在表单里直接调用即可:

@using (Html.BeginForm("Update", "Home"))
{
    @* 批量生成三个复杂类型的所有隐藏域 *@
    @Html.HiddenForModelProperties(m => m.Mandant)
    @Html.HiddenForModelProperties(m => m.Kreditor)
    @Html.HiddenForModelProperties(m => m.Positions)
    @Html.HiddenForModelProperties(m => m.Invoice.M_IV_ID)
    
    @* 原有表单代码不变 *@
    @Html.ValidationSummary()
    <div class="container">
        <!-- 原有输入框代码 -->
    </div>
    <button type="submit" class="btn btn-primary">Update</button>
}

这个方案的优势是完全不用手动写每个属性的隐藏域,自动适配你ViewModel的结构变更,无服务端依赖。

方案2:服务端缓存初始ViewModel合并数据

如果你的ViewModel属性非常多,生成大量隐藏域会增加请求体积,可以用这个方案:

  1. GET请求渲染表单时,将完整的ValidationViewModel按Invoice.M_IV_ID作为Key存入Session或者分布式缓存,设置合理的过期时间。
  2. POST接收提交的ViewModel后,先从缓存中取出完整的原始ViewModel,再将提交过来的已修改属性覆盖到原始模型上,就能得到完整的ViewModel。
[HttpPost]
public IActionResult Update(ValidationViewModel validationViewModel)
{
    if (ModelState.IsValid)
    {
        // 从缓存拿完整模型
        var fullModel = HttpContext.Session.Get<ValidationViewModel>(validationViewModel.Invoice.M_IV_ID.ToString());
        // 覆盖修改过的属性
        fullModel.Invoice.M_IV_KREDITOR = validationViewModel.Invoice.M_IV_KREDITOR;
        fullModel.Invoice.M_IV_INVOICEDATE = validationViewModel.Invoice.M_IV_INVOICEDATE;
        // 后续直接用fullModel操作
        fullModel.Invoice.Update();
    }
    return View("Index", validationViewModel);
}

方案3:AJAX提交JSON数据

如果可以接受异步提交,直接把整个ViewModel序列化为JSON放到请求体里提交,后端用[FromBody]接收即可,不需要任何隐藏域:

// 前端提交代码
$('form').submit(function(e) {
    e.preventDefault();
    var model = @Html.Raw(Json.Serialize(Model));
    // 覆盖输入框修改的属性
    model.Invoice.M_IV_KREDITOR = $('input[name="Invoice.M_IV_KREDITOR"]').val();
    model.Invoice.M_IV_INVOICEDATE = $('input[name="Invoice.M_IV_INVOICEDATE"]').val();
    $.ajax({
        url: '@Url.Action("Update", "Home")',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(model),
        success: function(res) {
            // 处理返回结果
        }
    });
});

后端接口修改为:

[HttpPost]
public IActionResult Update([FromBody] ValidationViewModel validationViewModel)
{
    // 逻辑不变
}

额外注意点

你的Positions属性目前只有get访问器,无论是模型绑定还是手动赋值都无法生效,需要修改为:

public List<M_IP_INVOICE_POS> Positions { get; set; }

内容的提问来源于stack exchange,提问作者user3793935

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 09:48:03