Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-mvc/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# MVC模型绑定动态列表_C#_Asp.net Mvc_Model Binding_Custom Model Binder - Fatal编程技术网

C# MVC模型绑定动态列表

C# MVC模型绑定动态列表,c#,asp.net-mvc,model-binding,custom-model-binder,C#,Asp.net Mvc,Model Binding,Custom Model Binder,我有一个动态列表,其中包含需要发布到MVC控制器/操作并绑定为类型化对象的s。我的问题的关键是我不知道如何在我的应用程序中手动选择任意发布的表单值。详情如下 我有一份美国各州的名单,每个州都有一份城市名单。州和城市都可以动态添加、删除和重新排序。比如: public class ConfigureStatesModel { public List<State> States { get; set; } } public class State { public st

我有一个动态列表,其中包含需要发布到MVC控制器/操作并绑定为类型化对象的
s。我的问题的关键是我不知道如何在我的应用程序中手动选择任意发布的表单值。详情如下

我有一份美国各州的名单,每个州都有一份城市名单。州和城市都可以动态添加、删除和重新排序。比如:

public class ConfigureStatesModel
{
    public List<State> States { get; set; }
}

public class State
{
    public string Name { get; set; }
    public List<City> Cities { get; set; }
}

public class City
{
    public string Name { get; set; }
    public int Population { get; set; }
}
ConfigureStates.cshtml

@model Models.ConfigureStatesModel
@foreach (var state in Model.States)
{
    <input name="stateName" type="text" value="@state.Name" />
    foreach (var city in state.Cities)
    {
        <input name="cityName" type="text" value="@city.Name" />
        <input name="cityPopulation" type="text" value="@city.Population" />
    }
}
我需要捕获表单值,理想情况下绑定为
列表
(或者等效为
配置状态模型
),如下所示:

但我不想手动解析它

还要注意的是,州列表的顺序和每个州的城市列表的顺序很重要,因为我坚持了显示顺序的概念。因此,这也需要从表单值中保留


我尝试过动态列表绑定的各种变体,如和。但是仅仅为了让绑定正常工作而放弃html并添加大量(容易出错的)javascript是错误的。表单值已经存在;这应该只是在服务器上捕获它们的问题。

我所看到的构建一个表单的唯一明显方式,即实际表示哪些城市属于哪个州,需要使用强类型助手

因此,我会使用类似于:

@model Models.ConfigureStatesModel
@对于(int-outer=0;outerm.States[outer].Name,new{@class=“state”})
对于(int-inner=0;innerm.States[outer].Cities[inner].Name)
@Html.TextBoxFor(m=>m.States[outer].Cities[inner].Population)
}
}
这将使用默认modelbinder可以处理的表单名称创建输入

需要额外工作的部分是处理重新订购。假设您已经在使用jQuery,我会使用类似的方法:

// Iterate through each state
$('.states').each(function (i, el) {
    var state = $(this);
    var input = state.find('input.state');

    var nameState = input.attr('name');
    if (nameState != null) {
        input.attr('name', nameState.replace(new RegExp("States\\[.*\\]", 'gi'), '[' + i + ']'));
    }

    var idState = input.attr('id');
    if (idState != null) {
        input.attr('id', idState.replace(new RegExp("States_\\d+"), i));
    }

    // Iterate through the cities associated with each state
    state.find('.cities').each(function (index, elem) {
        var inputs = $(this).find('input');

        inputs.each(function(){
            var cityInput = (this);

            var nameCity = cityInput.attr('name');
            if (nameCity != null) {
                cityInput.attr('name', nameCity.replace(new RegExp("Cities\\[.*\\]", 'gi'), '[' + index + ']'));
            }

            var idCity = cityInput.attr('id');
            if (idCity != null) {
                cityInput.attr('id', idCity.replace(new RegExp("Cities_\\d+"), index));
            }
        });
    });
});

这最后一点可能需要一些调整,因为它未经测试,但它类似于我以前所做的事情。每当视图中的项目被添加/编辑/删除/移动时,您都会调用此选项。

我想出了自己的解决方案。这是一个有点黑客,但我觉得它比其他选择更好。另一个解决方案和建议都涉及到修改标记和添加javascript以同步添加的标记——我在OP中明确说过我不想这样做。我觉得如果所述的
名称在DOM中已经按照您想要的方式排序,那么向
名称添加索引是多余的。添加javascript只是需要维护的另一件事,不必要的信息通过网络发送

无论如何。。我的解决方案涉及通过原始请求主体进行循环。我以前没有意识到这基本上只是一个url编码的查询字符串,经过简单的url解码后就很容易使用:

public class StatesBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
        string urlEncodedFormData = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
        var decodedFormKeyValuePairs = urlEncodedFormData
            .Split('&')
            .Select(s => s.Split('='))
            .Where(kv => kv.Length == 2 && !string.IsNullOrEmpty(kv[0]) && !string.IsNullOrEmpty(kv[1]))
            .Select(kv => new { key = HttpUtility.UrlDecode(kv[0]), value = HttpUtility.UrlDecode(kv[1]) });
        var states = new List<State>();
        foreach (var kv in decodedFormKeyValuePairs)
        {
            if (kv.key == "stateName")
            {
                states.Add(new State { Name = kv.value, Cities = new List<City>() });
            }
            else if (kv.key == "cityName")
            {
                states.Last().Cities.Add(new City { Name = kv.value });
            }
            else if (kv.key == "cityPopulation")
            {
                states.Last().Cities.Last().Population = int.Parse(kv.value);
            }
            else
            {
                //key-value form field that can be ignored
            }
        }
        return states;
    }
}
公共类StatesBinder:IModelBinder
{
公共对象绑定模型(ControllerContext ControllerContext,ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Request.InputStream.Seek(0,SeekOrigin.Begin);
字符串urlEncodedFormData=newstreamreader(controllerContext.HttpContext.Request.InputStream.ReadToEnd();
var decodedFormKeyValuePairs=urlEncodedFormData
.Split(“&”)
.选择(s=>s.Split('='))
其中(kv=>kv.Length==2&&!string.IsNullOrEmpty(kv[0])&&!string.IsNullOrEmpty(kv[1]))
.Select(kv=>new{key=HttpUtility.UrlDecode(kv[0]),value=HttpUtility.UrlDecode(kv[1]);
var states=新列表();
foreach(解码为MkeyValuePairs的var kv)
{
如果(kv.key==“stateName”)
{
states.Add(新状态{Name=kv.value,Cities=new List()});
}
否则,如果(kv.key==“cityName”)
{
states.Last().Cities.Add(新城市{Name=kv.value});
}
否则,如果(kv.key==“城市人口”)
{
states.Last().Cities.Last().Population=int.Parse(千伏值);
}
其他的
{
//可以忽略的键值表单字段
}
}
返回状态;
}
}
这假设(1)html元素在DOM上的顺序正确,(2)在POST请求正文中以相同的顺序设置,(3)在服务器上的请求流中以相同的顺序接收。据我所知,就我而言,这些都是有效的假设


再一次,这感觉像一个黑客,似乎不是很MVC-y。但这对我很有效。如果这恰好帮助了其他人,很酷。

你能分享你想传递给MVC action的课程吗?据我所知,您希望传递多个状态,为此,请使用
for
而不是
foreach
。摆脱您的自定义ModelBinder,因为它永远不会工作。您需要正确生成视图。请参阅一些选项注意:对于嵌套集合,您需要使用而不是
BeginCollectionItem
,或者如果您想在客户端执行所有操作-请参阅您需要的是为集合索引器添加一个隐藏输入,该索引器允许绑定非零、非连续的集合项-例如外部集合的
[HttpPost]
public ActionResult Save(List<State> states)
{
    //do some stuff
}
public class StatesBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        //California, Florida
        List<string> stateNames = controllerContext.HttpContext.Request.Form.GetValues("stateName").ToList();

        //Sacramento, San Francisco, Miami, Orlando
        List<string> cityNames = controllerContext.HttpContext.Request.Form.GetValues("cityName").ToList();

        //1000000, 2000000, 3000000, 4000000
        List<int> cityPopulations = controllerContext.HttpContext.Request.Form.GetValues("cityPopulation")
            .Select(p => int.Parse(p)).ToList();

        // ... build List<State> ...
    }
}
Request.InputStream.Seek(0, SeekOrigin.Begin);
string urlEncodedFormData = new StreamReader(Request.InputStream).ReadToEnd();
// Iterate through each state
$('.states').each(function (i, el) {
    var state = $(this);
    var input = state.find('input.state');

    var nameState = input.attr('name');
    if (nameState != null) {
        input.attr('name', nameState.replace(new RegExp("States\\[.*\\]", 'gi'), '[' + i + ']'));
    }

    var idState = input.attr('id');
    if (idState != null) {
        input.attr('id', idState.replace(new RegExp("States_\\d+"), i));
    }

    // Iterate through the cities associated with each state
    state.find('.cities').each(function (index, elem) {
        var inputs = $(this).find('input');

        inputs.each(function(){
            var cityInput = (this);

            var nameCity = cityInput.attr('name');
            if (nameCity != null) {
                cityInput.attr('name', nameCity.replace(new RegExp("Cities\\[.*\\]", 'gi'), '[' + index + ']'));
            }

            var idCity = cityInput.attr('id');
            if (idCity != null) {
                cityInput.attr('id', idCity.replace(new RegExp("Cities_\\d+"), index));
            }
        });
    });
});
public class StatesBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
        string urlEncodedFormData = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
        var decodedFormKeyValuePairs = urlEncodedFormData
            .Split('&')
            .Select(s => s.Split('='))
            .Where(kv => kv.Length == 2 && !string.IsNullOrEmpty(kv[0]) && !string.IsNullOrEmpty(kv[1]))
            .Select(kv => new { key = HttpUtility.UrlDecode(kv[0]), value = HttpUtility.UrlDecode(kv[1]) });
        var states = new List<State>();
        foreach (var kv in decodedFormKeyValuePairs)
        {
            if (kv.key == "stateName")
            {
                states.Add(new State { Name = kv.value, Cities = new List<City>() });
            }
            else if (kv.key == "cityName")
            {
                states.Last().Cities.Add(new City { Name = kv.value });
            }
            else if (kv.key == "cityPopulation")
            {
                states.Last().Cities.Last().Population = int.Parse(kv.value);
            }
            else
            {
                //key-value form field that can be ignored
            }
        }
        return states;
    }
}