2016-02-09 2 views
2

Это вопрос новичков от человека, изучающего ASP.NET MVC 5. Я хотел бы создать экран веб-приложения, где пользователь может редактировать древовидную структуру данных. Я не уверен, как это правильно, следуя лучшим практикам.Редактирование древовидных структур с ASP.MVC

Для простоты предположим, что это структура данных, два уровня, список драйверов, где каждый водитель может управлять несколько автомобилей:

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?

Примечание. Мне не нужны одностраничные веб-приложения, я просто хочу кэшировать пользовательские обновления на стороне клиента, прежде чем выполнять операцию HTTP post.

ОБНОВЛЕНИЕ, поэтому настоящее веб-приложение работает в классическом режиме MVC: сначала открывается список со списком драйверов, затем вид со списком автомобилей, затем один автомобиль. Для каждого представления есть отдельный HTTP-запрос.

Я хочу, чтобы пользователь мог редактировать всю структуру внутри одного представления, а затем нажать кнопку «Обновить». В этот момент я ожидаю, что обновленная модель POCO для всей структуры данных будет представлена ​​и доступна в моем контроллере MVC. Мне нужна клиентская инфраструктура JavaScript (Knockout, Angular, Aurelia и т. Д.) Для обработки и обновления индексаторов DefaultModelBinder для меня, поэтому мне не нужно было бы управлять индексаторами вручную (т. Е. <input name="Drivers[2].Cars[1].Name" ... /> и т. Д., Больше подробности в this q/a).

Награда будет вознаграждена за ответ, иллюстрирующий, как это сделать, с соответствующим образцом кода.

+1

Некоторые варианты динамического добавления (и удаления) элементов коллекции в ответах [здесь] (http://stackoverflow.com/questions/29161481/post-a-form-array-without-successful/29161796#29161796) и [здесь] (http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) –

+0

@StephenMuecke, правильно ли я понимаю, что Мне нужно генерировать правильные скрытые индексы в HTML (и обновлять их, поскольку пользователь редактирует данные в форме)? Не могли бы вы указать мне некоторые документы, объясняющие, как данные модели POCO собираются обратно из HTML, прежде чем они будут отправлены обратно контроллеру и как это можно настроить? Спасибо. – avo

+1

Да, это правильно. Индексаторы коллекции должны начинаться с нуля и быть последовательными, если вы не добавите скрытое поле для свойства «Index», которое позволяет «DefaultModelBinder» соответствовать нескольким индексаторам. –

ответ

2

Так что вы хотели, чтобы помочь вам с необходимой совсем немного времени, чтобы построить, но на самом деле очень просто (Чтобы быть справедливым, не было достаточно информации, в большинстве основных учебников KO, чтобы сделать все это.)

Итак, я построил одну страницу и один контроллер 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(), который priovides страницы с образцами данных для работы с.

Здесь вы, вероятно, будете делать что-то вроде запроса к вашему долгосрочному хранилищу для дерева, которое будет отображаться на стороне клиента. Скорее всего, он будет отмечен каким-то идентификатором, но поскольку эти детали не были в вашем вопросе, я их пропустил. Вы можете легко понять это с помощью этого примера.

Самая интересная часть на самом деле находится на той странице, где я использовал нокаут для создания рендерера для структуры данных 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 ahs выполнил запрос сервера для данных. Это делается только ради удовольствия (я тоже экспериментировал).

Вот ViewModels на оставшуюся часть лиц:

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. Я сделал это намеренно, так что MVC не имеет проблем при сопоставлении их с объектом C#, когда мы закончили работу с ними на стороне клиента. Это может быть не очень хорошая работа в долгосрочной перспективе, но это будет сделано для простоты.

Так что в основном происходит, когда наш DriversViewModel запрашивает элементы с сервера, он отображает все данные в ночные развлекательные абстракции, которые отслеживаются нокаутом по мере их изменения. Все, что остается на самом деле сказать Нокаут использовать этот ViewModel:

ko.applyBindings(new DriversViewModel()); 

Теперь Нокаут готов работать с этими объектами, и настало время для нас, чтобы построить часть пользовательского интерфейса.

страница, которая использует эти привязки 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 вы используете в этом контексте. (Это важно, когда вы используете разные функции добавления/удаления, определенные на разных моделях ViewModels.)

И все. Вот скриншот полученного решения для хорошей меры: Screenshot of the resulting page

И хотя он выглядит довольно грязным, он выполняет свою работу. При нажатии кнопки Добавить кнопки, автомобили или драйверы. Щелчок по кнопке Обновление приведет к сборке всего дерева и отправке его обратно на сервер, где он будет преобразован в POCO с помощью магии ASP.NET MVC.

Здесь представлены пастихины кода, чтобы вы могли просто скопировать-вставить и посмотреть его сами. Вам придется немного поиграть с проектом MVC, но я считаю, что вы можете справиться с этим.

Страница: http://pastebin.com/2aGkEHEN

Контроллер: http://pastebin.com/nZaufcpw

Одно важное замечание:

Если вы действительно хотите сделать что-то вроде этого, вы можете захотеть, чтобы отслеживать пользовательские изменения данные вместо этого просто получают всю измененную структуру и перезаписывают старые данные. Вместо того, чтобы найти нужный подход, я показал вам в этом примере, я бы попытался отслеживать изменения, которые пользователь делает с деревом, а затем отправляет на сервер вместо целой структуры какой-то набор изменений. Таким образом, вы можете применить набор изменений в транзакции и добиться того же результата, но с меньшей пропускной способностью и большей согласованностью.

+0

Спасибо, это, безусловно, один из способов сделать это. Кажется, что ваш контроллер используется здесь очень как веб-API. Я предположил, что можно было бы использовать Razor для рендеринга исходного представления с помощью '@ Model' и ** позволить Knockout управлять скрытыми индексаторами MVC ** (например,' ', упомянутое @StephenMuecke в его комментариях к вопросу). Разве это не вариант с нокаутом? Может быть, любая другая структура? – avo

+0

@avo, о чем вы говорите. Это решение не является WebAPI, это ASP.NET MVC, а метод действия индекса отображает исходное представление, если вы хотите использовать Razor вместо KnockoutJS для выполнения первоначальной рендеринга вашей модели вместо KnockoutJS, вы можете и по-прежнему иметь свой привязка к KnockoutJS. Кроме того, в этом решении Knockout управляет скрытыми индексаторами MVC, как вы выразились. –

+0

@BrianOgden, я признаю, что пропустил это. Сейчас я играю с ним в режиме отладчика/F12, и я понимаю, что вы имеете в виду. Я попытаюсь сделать это с помощью Razor с привязками KnockoutJS, и именно этого я и хочу. – avo

Смежные вопросы