我正在将模型传递给视图,其中的属性是书籍的集合。在我看来,我使用foreach循环来创建我的集合中的书籍表,每个表都有名称,作者等。
我希望用户能够在客户端添加/编辑/删除书籍。然后我希望能够将模型传回控制器,并收集反映所做更改的书籍。
这可能吗?
答案 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/