使用list属性将模型返回到控制器

时间:2013-08-14 15:30:12

标签: c# asp.net-mvc razor

我正在将模型传递给视图,其中的属性是书籍的集合。在我看来,我使用foreach循环来创建我的集合中的书籍表,每个表都有名称,作者等。

我希望用户能够在客户端添加/编辑/删除书籍。然后我希望能够将模型传回控制器,并收集反映所做更改的书籍。

这可能吗?

3 个答案:

答案 0 :(得分:1)

在不使用ajax / jquery / knockout的情况下解决了它:

基本上我需要将cshtml页面包装在@using(Html.BeginForm(〜)){}标记中,然后使用for循环(不是foreach)来显示List中的每个项目。然后我需要为列表中的每个项目创建一个@ Html.HiddenFor。当我提交表单时,我将模型作为参数,并填充列表中的项目。我无法显示我的实际代码,所以我匆匆重新标记了一些关键变量,所以我希望你们能够理解它,但这实际上就是我如何使它工作

这是控制器

[HttpGet]
public ActionResult BookStore(int storeId)
{
    //model contains a list property like the following:
    //public List<Books> BooksList { get; set; }
    //pass the model to the view
    var model = new BookStoreModel();

    return View(model);
}

这是视图

@model BookStore.Models.BookStoreModel
@using (Html.BeginForm("BookStoreSummary", "BookStore", FormMethod.Post))
{ 
<fieldset>

@Html.HiddenFor(model => model.Id)
@Html.HiddenFor(model => model.BookId)
@Html.HiddenFor(model => model.LastModified)
//some html stuff here
<table id="users" class="ui-widget ui-widget-content">
<thead>
  <tr class="ui-widget-header BookTable">
    <th>@Html.DisplayNameFor(model => model.BookList.FirstOrDefault().Title)        </th>
    <th>@Html.DisplayNameFor(model => model.BookList.FirstOrDefault().Author)</th>

   </tr>
</thead>
<tbody>
@for (int i = 0; i < Model.BookList.Count; i++)
{
        <tr>
            <td>
                @Html.HiddenFor(model => model.BookList[i].Author)
                @Html.DisplayFor(model => model.BookList[i].Author)
            </td>
            <td>
                @Html.HiddenFor(model => model.BookList[i].BookId)
                @Html.HiddenFor(model => model.BookList[i].Title)
                @Html.DisplayFor(model => model.BookList[i].Title)
            </td>
        </tr>
    }
</tbody>
</table>

</fieldset>
}

和后置控制器:

 [HttpPost]
 //[AcceptVerbs("POST")]
 public ActionResult BookStoreSummary(BookStoreModel model)
 {
    //do stuff with model, return
    return View(model);
 }

答案 1 :(得分:0)

是的,有可能

假设您有一个表单,并且您有一个名为“Books”的集合。如果要以编程方式添加新书,可以使用jQuery和ajax。你需要一些助手课程。

以下课程可帮助您创建唯一的书籍项目,以添加到视图中的书籍集合中。如您所知,每个字段都应该有唯一的前缀,因此模型binder可以区分表单元素

public static class HtmlClientSideValidationExtensions
{
        public static IDisposable BeginAjaxContentValidation(this HtmlHelper html, string formId)
        {
            MvcForm mvcForm = null;

            if (html.ViewContext.FormContext == null)
            {
                html.EnableClientValidation();
                mvcForm = new MvcForm(html.ViewContext);
                html.ViewContext.FormContext.FormId = formId;
            }

            return new AjaxContentValidation(html.ViewContext, mvcForm);
        }

        private class AjaxContentValidation : IDisposable
        {
            private readonly MvcForm _mvcForm;
            private readonly ViewContext _viewContext;

            public AjaxContentValidation(ViewContext viewContext, MvcForm mvcForm)
            {
                _viewContext = viewContext;
                _mvcForm = mvcForm;
            }

            public void Dispose()
            {
                if (_mvcForm != null)
                {
                    _viewContext.OutputClientValidation();
                    _viewContext.FormContext = null;
                }
            }
        }
    }

    public static class CollectionValidation
    {

        private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

        public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
        {
            var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
            string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

            // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
            html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

            return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
        }

        public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
        {
            return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
        }

        private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
        {
            // We need to use the same sequence of IDs following a server-side validation failure,  
            // otherwise the framework won't render the validation error messages next to each item.
            string key = idsToReuseKey + collectionName;
            var queue = (Queue<string>)httpContext.Items[key];
            if (queue == null)
            {
                httpContext.Items[key] = queue = new Queue<string>();
                var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
                if (!string.IsNullOrEmpty(previouslyUsedIds))
                    foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                        queue.Enqueue(previouslyUsedId);
            }
            return queue;
        }

        private class HtmlFieldPrefixScope : IDisposable
        {
            private readonly TemplateInfo templateInfo;
            private readonly string previousHtmlFieldPrefix;

            public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
            {
                this.templateInfo = templateInfo;

                previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
                templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
            }

            public void Dispose()
            {
                templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
            }
        }
    }

然后我们假设你有一个部分视图来添加这样的书:

@model Models.Book
 @using (Html.BeginAjaxContentValidation("form"))
    {
        using (Html.BeginCollectionItem("Books"))
        {



        <div class="fieldcontanier">
            @Html.LabelFor(model => model.Title)

            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="fieldcontanier">
            @Html.LabelFor(model => model.Author)

            @Html.EditorFor(model => model.Author)
            @Html.ValidationMessageFor(model => model.Author)
        </div>

       ...

        }
    }

并假设在表单中有一个“添加新书”链接,您在jQuery中定义了以下事件:

 $('a ').click(function (e) {
                e.preventDefault();
                $.ajax({
                    url: '@Url.Action("NewBook")',
                    type: 'GET',
                    success: function (context) {
                        $('#books').append(context);


                        $("form").removeData("validator");
                        $("form").removeData("unobtrusiveValidation");
                        $.validator.unobtrusive.parse("form");
                    }
                });
            });

在上面的代码中,首先请求操作NewBook,它返回我前面提到的部分视图,然后在页面中的其他书籍之后加载,然后通过不显眼的验证来应用我们使用最后的树线。

答案 2 :(得分:0)

是的,绝对可能。我目前在客户端上使用KnockOut,它将允许您绑定到Javascript对象的集合,将每个项目呈现为模板,添加,删除,然后将整个集合发回服务器进行处理。您将需要处理已删除书籍的状态,并将其隐藏在绑定中,但这一切都是可行的。

以下是您在视图中需要的KO语法:

<table>
   <!-- ko foreach: {data: books } -->
   <tr>
      <td data-bind="text: title" />
   </tr>
   <!-- /ko -->
</table>

这将为书籍中的每个项目创建一个包含1行的表格。这些对象需要一个标题&#39;属性是表中唯一的值。

Knockout是一个很棒的图书馆,我最近在学习和开发方面有很多乐趣。您可以在此处获取有关其项目页面的更多信息:http://knockoutjs.com/