it-roy-ru.com

ASP.Net MVC и состояние - как сохранить состояние между запросами

Как довольно опытный разработчик ASP.Net, только недавно начавший использовать MVC, я немного борюсь за то, чтобы изменить свое мышление с традиционного способа управления сервером и обработчиком событий на более динамичный MVC. Я думаю, что медленно добираюсь туда, но иногда "магия" MVC сбивает меня с толку.

Мой текущий сценарий заключается в создании веб-страницы, которая позволяет пользователю просматривать локальный файл, загружать его на сервер и повторять, пока у него не появится список файлов для работы. Когда он доволен списком файлов (который будет отображаться в сетке на странице), он нажмет кнопку, чтобы обработать файлы и извлечь некоторые данные, которые будут храниться в базе данных.

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

Я думаю, мне лучше опубликовать мою довольно неполную попытку кодирования этого, чтобы объяснить проблему.

Чтобы сохранить данные списка файлов, я создал модель представления, которая представляет собой типизированный список файлов вместе с некоторыми дополнительными метаданными:

public class ImportDataViewModel
{
    public ImportDataViewModel()
    {
        Files = new List<ImportDataFile>();
    }

    public List<ImportDataFile> Files { get; set; }
...

В представлении у меня есть форма для просмотра и загрузки файла:

<form action="AddImportFile" method="post" enctype="multipart/form-data">
  <label for="file">
    Filename:</label>
  <input type="file" name="file" id="file" />
  <input type="submit" />
  </form>

Представление использует модель представления в качестве своей модели:

@model MHP.ViewModels.ImportDataViewModel

Это отправит файл на мои действия:

public ActionResult AddImportFile(HttpPostedFileBase file, ImportDataViewModel importData)
    {

        if (file.ContentLength > 0)
        {
            ImportDataFile idFile = new ImportDataFile { File = file };
            importData.Files.Add(idFile);
        }

        return View("DataImport", importData);
    }

Это действие возвращает представление для страницы DataImport вместе с экземпляром viewmodel, содержащим список файлов.

Это работает хорошо до определенного момента, я могу просмотреть файл и загрузить его, и я могу видеть данные viewmodel внутри действия, а затем также, если я помещаю точку останова в представление и отлаживаю "this.Model", все хорошо.

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

В примерах MVC, через которые я прошел, экземпляр модели "магически" передается методу действия в качестве параметра, так почему же он сейчас пуст?

Я предполагаю, что настоящая проблема - мое ограниченное понимание MVC, и что, вероятно, есть очень простое решение для этого. В любом случае, я был бы чрезвычайно признателен, если бы кто-нибудь указал мне правильное направление.

51
TMan

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

В первую очередь меня поразило то, что у вас может быть контроллер со строго типизированным объектом в качестве параметра, например так:

public ActionResult DoSomething(MyClass myObject)...

Этот объект возник из того же контроллера:

...
return View(myObject);
...

Это заставило меня поверить, что объект жил на протяжении этих двух шагов, и что я каким-то образом мог ожидать, что вы могли бы отправить его в представление, сделать что-то и затем "волшебным образом" снова вернуть его контроллеру.

Прочитав о привязке модели, я понял, что это, конечно, не так. Представление полностью мертвое и статичное, и если вы не сохраните информацию где-либо, оно исчезнет.

Возвращаясь к проблеме, которая заключалась в выборе и загрузке файлов с клиента, и создании списка этих файлов для отображения, я понял, что в целом существует три способа хранения информации между запросами в MVC:

  1. Вы можете хранить информацию в полях формы в представлении и позже публиковать ее на контроллере.
  2. Вы можете сохранить его в каком-то хранилище, например файл или база данных
  3. Вы можете сохранить его в памяти сервера, обрабатывая объекты, которые существуют в запросах, например переменные сеанса

В моем случае у меня было в основном два типа информации: 1. Метаданные файла (имя файла, размер файла и т.д.) 2. Содержимое файла

Подход "по книге", вероятно, будет заключаться в том, чтобы хранить метаданные в полях формы, а содержимое файла - в файле или в БД. Но есть и другой способ. Поскольку я знаю, что мои файлы довольно малы, и их будет всего несколько, и это решение никогда не будет развернуто в ферме серверов или аналогичной, я хотел изучить вариант # 3 переменных сеанса. Файлы также не представляют интереса для сохранения после сеанса - они обрабатываются и удаляются, поэтому я не хотел хранить их в своей базе данных.

После прочтения этой превосходной статьи: Доступ к данным сеанса ASP.NET с помощью Dynamics

Я был убежден. Я просто создал класс sessionbag, как описано в статье, и затем я мог сделать следующее в моем контроллере:

    [HttpPost]
    public ActionResult AddImportFile(HttpPostedFileBase file)
    {

        ImportDataViewModel importData = SessionBag.Current.ImportData;
        if (importData == null) importData = new ImportDataViewModel();

        if (file == null)
            return RedirectToAction("DataImport");

        if (file.ContentLength > 0)
        {
            ImportDataFile idFile = new ImportDataFile { File = file };
            importData.Files.Add(idFile);
        }

        SessionBag.Current.ImportData = importData;

        return RedirectToAction("DataImport");
    }

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

Дополнительный бонус от использования SessionBag заключается в том, что если пользователь войдет в другой пункт меню, а затем вернется, список файлов все равно будет там. Это не будет иметь место, например, при выборе полей формы/опции хранения файлов.

В заключение я понимаю, что SessionBag очень легко злоупотреблять, учитывая простоту использования. Но если вы используете его для того, для чего он предназначен, а именно для данных сессий, я думаю, что это может быть мощным инструментом.

48
TMan

Относительно загрузки

1) Может быть, стоит рассмотреть загрузчик AJAX с HTML, чтобы позволить вашему пользователю выбрать несколько файлов до их отправки на сервер. Этот загрузчик файлов BlueImp Jquery AJAX довольно удивителен с довольно хорошим API: Загрузка файла Jimpery Blueimp . Это позволит пользователям перетаскивать или многократно выбирать несколько файлов и редактировать порядок файлов, включать/исключать и т.д. Затем, когда они счастливы, они могут нажать "загрузить" для отправки на ваш контроллер или обработчик загрузки для обработки на стороне сервера.

2) Вы можете сделать каждую загрузку сохраняемой в базу данных, хотя вам нужно будет перезагрузить всю страницу и написать некоторую дополнительную модель представления и код бритвы для достижения эффекта листинга. Это, вероятно, не будет реагировать ...

Что касается сохранения состояния WebForms/MVC

Сохранение состояния между запросами - это чёрная магия и вуду. Зайдя в ASP.NET MVC, поймите, что веб-приложения взаимодействуют, используя запросы и ответы. Так что переходите к тому, чтобы воспринимать Интернет как лицо без гражданства и развиваться оттуда! Когда ваша модель публикуется через ваш контроллер, она исчезает вместе с любыми переменными в контроллере! Однако, прежде чем он уйдет, вы можете сохранить его содержимое в базе данных для последующего поиска.

Веб-приложения не могут сохранять истинное состояние, как настольные приложения. Существует множество способов использования Ajax-фреймворков и некоторых инструментов Voodoo, которые люди используют для имитации состояния в среде HTTP. И симуляция состояния - это всего лишь ложная имитация состояния. ASP.NET Web Forms старается смоделировать состояние как можно лучше, скрывая от HTTP природу HTTP без состояния. Вы можете столкнуться с большой головной болью, пытаясь использовать свой собственный код AJAX в тандеме с кодом разметки Web Forms и собственной платформой Ajax.

Я очень рад, что вы изучаете MVC

Если не считать всех шуток, если вы получите менталитет MVC/HTTP/Statelessness, будет очень легко применить шаблоны к другим очень популярным средам, таким как Ruby на Rails, SpringMVC (Java), Django (python), CakePHP и т. д. Эта простая передача знаний поможет вам стать намного лучшим разработчиком и получить действительно хорошие результаты в Ajax.

Я действительно рад, что вы изучаете MVC 3, я проходил несколько стажировок в некоторых очень крупных фирмах, в которых были эти сумасшедшие большие проекты ASP.NET Web Forms, где код летал повсюду, просто чтобы изменить несколько числовых значений в базе данных. (-_- ') чувствовал, что я использовал ножницы, чтобы связать носок ребенка. Одно легкое неправильное движение, и все сломалось. Было похоже на развитие в PHP, вы вспотели и не совсем уверены, что произошло и по какой линии. Было почти невозможно отладить и обновить.

11
Max Alexander