这是来自学习ASP.NET MVC 5的人的新手问题。我想创建一个Web应用程序屏幕,用户可以在其中编辑树状数据结构。在遵循最佳实践的同时,我不确定它是如何正确的。
为简单起见,我们假设这是一个两级数据结构,每个驱动程序可以驱动多辆汽车的驱动程序列表:
public class Car
{
public string Name; { get; set; }
public string Code; { get; set; }
}
public class Driver
{
public string FullName { get; set; }
public List<Cars> { get; set; }
}
public class Model
{
public List<Driver> Drivers { get; set; }
}
我需要能够在同一屏幕上添加/删除/编辑驱动程序(及其汽车)。这是否意味着每个单个更改用户(例如,添加新车,然后更改其Code
)应该需要表单提交(即,命中我的控制器)并发回一个对客户的新观点?
如果是这样,我是否需要使用MVVM框架(如Angular或Knockout),如果我想在客户端允许多个模型更改,然后将所有更新提交回控制器?或者是可以用裸ASP.NET MVC完成的事情吗?
注意我不想要单页Web应用程序,我只想在进行HTTP post操作之前在客户端缓存用户更新。
更新,所以这个网络应用程序目前以经典的MVC方式工作:首先是一个包含驱动程序列表的视图,然后是包含汽车列表的视图,然后是一辆汽车。有一个单独的HTTP请求来呈现每个视图。
我想要的是让用户在同一个视图中编辑整个结构,然后点击Update按钮。那时,我希望提交一个更新的POCO模型来提交整个数据结构,并在我的MVC控制器中提供。 我想要一个客户端JavaScript框架(Knockout,Angular,Aurelia等)为我处理生成和更新DefaultModelBinder
索引器,所以我不必手动管理索引器(即<input name="Drivers[2].Cars[1].Name" ... />
等,this q/a)中的更多详细信息。
赏金将会得到答案,说明如何做到这一点,与相关的示例代码。
答案 0 :(得分:2)
所以你希望我们帮你的东西需要相当多的时间来构建,但实际上非常简单(公平地说,在大多数基本的KO教程中都有足够的信息来完成所有这些。)
所以我用3种方法构建了一个页面和一个MVC控制器:一个用于页面本身,两个用于 GET ting或 POST 数据。
这是控制器的代码:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult PostDriversModel(DriversModel model)
{
return Json(new { Success = true }, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult GetDriversModel()
{
var model = new DriversModel
{
Drivers = new List<Driver>
{
new Driver
{
FullName = "John Doe",
Cars = new List<Car>
{
new Car {Code = "car0", Name = "Amazing car"},
new Car {Code = "car1", Name = "Cool car"}
},
},
new Driver
{
FullName = "Johnny Dough",
Cars = new List<Car>
{
new Car {Code = "car2", Name = "Another Amazing car"}, new Car {Code = "car3", Name = "Another Cool car"}
}
},
}
};
return Json(model, JsonRequestBehavior.AllowGet);
}
}
正如您所看到的,控制器是非常准确的,并且它们的最大方法都是GetDriversModel(),它使用样本数据来创建页面。
在这里,你可能会做一些事情,例如查询你的长期存储空间,以便在客户端渲染树。它很可能会被标记为某种ID,但由于这些细节不在你的问题中,我已经省略了它们。你可以通过这个例子轻松搞清楚。
最有趣的部分实际上是在我使用knockout为DriversModel数据结构构建渲染器的页面上。首先,让我们检查一下JavaScript:
KO视图的核心是ViewModel:
function DriversViewModel() {
var self = this;
self.Drivers = ko.observableArray([]);
self.addDriver = function() {
self.Drivers.push(new DriverModel({ FullName: 'Mr. Noname', Cars: [] }));
};
self.removeDriver = function(driver) {
self.Drivers.remove(driver);
};
self.update = function() {
$.ajax("/Home/PostDriversModel", {
data: ko.toJSON({ Drivers: self.Drivers }),
type: "post", contentType: "application/json",
success: function () { alert('Success!'); }
});
}
$.getJSON('/Home/GetDriversModel', function (data) {
var drivers = data.Drivers.map(function (driver) { return new DriverModel(driver); });
drivers.push(new DriverModel({ Cars: [], FullName: 'Mr Nocars' }));
self.Drivers(drivers);
});
}
在其中我们定义了几种从树中添加/删除驱动程序的方法,以及将内容发布回服务器的方法。 ViewModel非常简单(就像这个例子中的所有内容一样)。请注意,在JS完成查询服务器数据之后,我已经在列表中添加了另一个随机驱动程序。这只是为了它的乐趣(我也在试验)。
以下是其余实体的ViewModel:
function CarModel(data) {
var self = this;
self.Code = ko.observable(data.Code);
self.Name = ko.observable(data.Name);
}
function DriverModel(data) {
var self = this;
self.addCar = function () {
self.Cars.push(new CarModel({ Name: 'Tank', Code: '__' }));
};
self.removeCar = function (car) {
self.Cars.remove(car);
};
self.Cars = ko.observableArray(data.Cars.map(function(car) { return new CarModel(car); }));
self.FullName = ko.observable(data.FullName);
}
正如您所看到的,所有这些内部都有一些初始化逻辑,我们将从服务器获取的JSON对象映射到客户端抽象。您还可以注意到它们不遵循JavaScript对象的命名约定。我故意这样做,以便当我们在客户端使用它们时,将它们映射到C#对象时,MVC没有问题。 从长远来看,这可能不是一件好事,但为了简单起见,这样做。
所以基本上发生的事情是当我们的DriversViewModel从服务器请求项目时,它将所有数据映射到Knockout友好的抽象,当Knockout被更改时,这些抽象被Knockout跟踪。剩下的就是告诉Knockout使用这个ViewModel:
ko.applyBindings(new DriversViewModel());
现在淘汰赛已经准备好使用这些对象了,现在是我们构建UI部分的时候了。
使用这些KO绑定的页面如下所示:
<div>
<a href="#" data-bind="click: $root.addDriver">Add Driver</a>
<a href="#" data-bind="click: $root.update">Update</a>
</div>
<ul data-bind="foreach: Drivers, visible: Drivers().length > 0">
<ul>
<div>
<input data-bind="value: FullName"/>
<a href="#" data-bind="click: $parent.removeDriver">Delete</a>
<a href="#" data-bind="click: addCar">Add Car</a>
</div>
<ul class="no-cars" data-bind="visible: Cars().length == 0">No cars D:</ul>
<ul data-bind="foreach: Cars, visible: Cars().length > 0">
<li>
<div>
<a href="#" data-bind="click: $parent.removeCar">Delete</a>
<label>Car Name:</label> <input data-bind="value: Name"/>
<label>Car Code:</label> <input data-bind="value: Code"/>
</div>
</li>
</ul>
</ul>
</ul>
正如你所看到的,没有什么比这更棘手的了。设置这一切最棘手的部分是知道要使用哪些数据绑定指令,并跟踪您在哪个上下文中实际使用的ViewModel。 (当您使用在不同ViewModel上定义的不同添加/删除功能时,这很重要。)
虽然它看起来很混乱,但它完成了工作。 当您单击相应的添加按钮时,会添加汽车或驱动程序。 单击更新按钮将组装整个树并将其发回服务器,然后将其转换为具有ASP.NET MVC魔力的POCO。
以下是代码的粘贴代码,以便您可以自行复制粘贴并查看它。你必须稍微调整一下MVC项目,但我相信你可以处理它。
页: http://pastebin.com/2aGkEHEN
控制器: http://pastebin.com/nZaufcpw
一个重要提示:
如果你真的想做这样的事情,你可能想要跟踪用户如何更改数据,而不是只获取整个更改的结构并覆盖旧数据。 我没有在示例中向您展示过的naiive方法,而是尝试跟踪用户对树所做的更改,然后将某种 changeset 发送到服务器而不是整个结构。这样,您可以在事务中应用变更集并获得相同的结果,但带宽更少,更一致。