使用ASP.MVC

时间:2016-02-09 02:54:28

标签: c# .net asp.net-mvc asp.net-mvc-5

这是来自学习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)中的更多详细信息。

赏金将会得到答案,说明如何做到这一点,与相关的示例代码

1 个答案:

答案 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上定义的不同添加/删除功能时,这很重要。)

就是这样。以下是最佳解决方案的屏幕截图: Screenshot of the resulting page

虽然它看起来很混乱,但它完成了工作。 当您单击相应的添加按钮时,会添加汽车或驱动程序。 单击更新按钮将组装整个树并将其发回服务器,然后将其转换为具有ASP.NET MVC魔力的POCO。

以下是代码的粘贴代码,以便您可以自行复制粘贴并查看它。你必须稍微调整一下MVC项目,但我相信你可以处理它。

页: http://pastebin.com/2aGkEHEN

控制器: http://pastebin.com/nZaufcpw

一个重要提示:

如果你真的想做这样的事情,你可能想要跟踪用户如何更改数据,而不是只获取整个更改的结构并覆盖旧数据。 我没有在示例中向您展示过的naiive方法,而是尝试跟踪用户对树所做的更改,然后将某种 changeset 发送到服务器而不是整个结构。这样,您可以在事务中应用变更集并获得相同的结果,但带宽更少,更一致。