воскресенье, 18 мая 2008 г.

MVC и Ajax

Создавая проект на основе MVC, вы можете использовать Ajax, причем сделать это совсем несложно. Кто-то захочет использовать самописный код, а может быть, библиотеку Microsoft Ajax, я же предпочитаю использовать простую и удобную библиотеку Prototype. Единственный видимый ее недостаток - размер (123 К), который устраняется сжатием. Например, можно воспользоваться вот этим онлайновым компрессором, после которого файл уменьшается до 50К, что вполне приемлемо.

Задача
Мне требуется сделать следующую, довольно простую штуку: есть два комбо, в одном перечислены страны, а в другом надо обновлять список городов для данной страны. Т.е. выбрали мы, например, Russia в первом комбо, во втором должны появиться Moscow, SPb и так далее. И естественно, без перезагрузки страницы.

План
Задача на первый взгляд простая и даже тривиальная, и решается она действительно просто. Но давайте в учебных целях подробно рассмотрим план решения.
1. БД - в базе должны были страны и города
2. Entity Framework - интерфейс для доступа к данным.
3. Модель - должен обеспечивать как выдачу списка доступных стран, так и списка городов для страны.
4. Контроллер - должен иметь методы действий для отображения всей страницы (на которой размещаются комбо) и части (обновление второго комбо).
5. Представления - отображение данных.
6. Ajax (prototype) - код для подтягивания данных по событию комбо.

1. База данных
Заведем две таблицы - города и страны. Я не очень доверяю числовым идентификаторам, т.е. я им доверяю, но связывать такие справочные таблицы предпочитаю с помощью более долговечных вещей. Поэтому я ввожу поле кода для страны, а в таблице городов делаю ссылку на это поле (FK т.е.).

Тут есть еще дистрикты, но они нам в данный момент не интересны.
После этого можно забить нужные вам данные (в дальнейшем это, конечно, надо делать с помощью админского сайта).

2. Entity Framework
После создания таблиц надо сгенерировать код для доступа к ним. Можно просто апдейтить нашу модель, но у меня, например, студия сглюкнула и пришлось удалять модель полностью, а затем создавать заново. Чтобы обновить модель, надо перейти в режим Model browser (дважды щелкнуть на файле .edmx - мне такой способ не очень нравится, но другого я не знаю). Затем выбрать корневой элемент в дереве и выполнить команду Update model from database. Далее вы просто выбираете новые таблицы и жмете Finish. Если все пройдет нормально, вы получите обновленную модель и обновленный код доступа к данным.

3. Модель
В каталоге Models проекта создадим два класса - с помощью первого будем извлекать статические данные для первого комбобокса (страны), с помощью второго - динамического для комбо с городами.
Итак, первый класс - StaticData - содержит одно публичное свойство и приватный метод, который забирает из базы список стран.

  public class StaticData
  {
    public List<Country> CountryList
    {
      get
      {
        return GetAllCountries();
      }
    }
    private static List<Country> GetAllCountries()
    {
      List<Country> countries = null;
      using (DataCoreConnection context = new DataCoreConnection())
      {
        countries = (from c in context.Country
               select c).ToList();
      }
      return countries;
    }
    
  }



Код простейший, добавлю лишь, что DataCoreConnection - это класс типа System.Data.Objects.ObjectContext из файла, сгенерированного при построении модели.
Второй класс аналогичен первому, за исключением конструктора, в который передается код страны:
public class Cities
  {
    private string countryCode;
    public Cities(string CountryCode)
    {
      countryCode = CountryCode;
    }
    public List<City> CitiesForCountry
    {
      get
      {
        return GetCitiesForCountry(countryCode);
      }
    }
    private List<City> GetCitiesForCountry(string CountryCode)
    {
      List<City> list = null;
      using (DataCoreConnection context = new DataCoreConnection())
      {
        list = (from c in context.City
                where c.Country.Code == CountryCode
                select c).ToList();
      }
      return list;
    }
  }



4. Контроллер
Контроллер у нас уже есть - HomeController (поскольку наши комбобоксы располагаются на главной странице). Сюда мы добавим один-единственный метод:
public void UpdateCities(string code)
    {
      Cities cities = new Cities(code);
      RenderView("SelectCity", cities);
    }



Здесь мы всего лишь получаем наши города и передаем экземпляр объекта представлению SelectCity.
Не забываем о роутинге - в файле Global.asax добавим строку:
      routes.Add(new Route("Home/UpdateCities/{code}", new RouteValueDictionary(new { controller = "Home", action = "UpdateCities" }),
        new MvcRouteHandler()));


Обратите внимание на последний элемент - {code} - это код города, который передается контроллеру. Контроллер принимает его как string code.

5. Представления - отображение данных.
Контролы помещаются на странице (т.е. представлении) Index.aspx. Поскольку страница с момента загрузки должна содержать некоторые статические данные, это должна быть не просто ViewPage, а в нашем случае ViewPage<StaticData>. Сначала отобразим комбобокс со списком доступных стран:



Как видим, мы получаем данные напрямую из ViewData, как будто ViewData - это объект типа StaticData (так оно и есть). Очень удобно. Кого-то может смутить тот факт, что разметка перемешивается с кодом. Как же так? Ведь обещали полное отделение кода! Мммм... ну.... в-общем, ничего страшного. Кода все-таки очень мало, и мне он кажется логическим дополнением к разметке.
Обращаю внимание на объявление обработчика:

onchange="UpdateCitiesList();"


Это функция JavaScript, которая будет подтягивать список городов при изменении значения комбобокса со странами (эту функцию рассмотрим чуть позже).
А пока займемся представлением, обновляющим второй комбобокс. Да-да, комбобокс будет обновлен целым представлением! Добавляем новое представление SelectCity, которое, конечно, объявляем так:

  public partial class SelectCity : ViewPage<Cities>
  {
  }



А вот код, генерирующий содержимое комбобокса:



6. Ajax
Ну а теперь посмотрим, какая функция подтягивает список городов (вернемся к Index.aspx):

  1: <script type="text/javascript" language="javascript">
  2:   function UpdateCitiesList()
  3:   {
  4:     if ($('selCountry').value == "0")
  5:       return;
  6:       var url = '/Home/UpdateCities/' + $('selCountry').value;
  7:     new Ajax.Updater('selCities', url,
  8:     {
  9:       method: 'get'
 10:     }
 11:         );
 12:   }
 13: </script>



В строке 4 мы проверяем, не выбран ли нулевой элемент выпадающего списка (тот, который содержит текст "Select ..."). В этом случае просто покидаем функцию. Далее формируем url, к которому будем обращаться - это обычный адрес в MVC, нам надо вызвать контроллер Home, метод UpdateCities, и передать код страны. В строке 7 мы вызываем очень удобный метод Ajax.Updater - он обновляет содержимое нужного нам контрола. Сигнатура метода проста:
new Ajax.Updater(container, url[, options])

Container - это контрол, который требуется обновить; url - вызываемый url; options - мы указали только метод передачи запроса (полный список параметров см. на сайте Prototype).

Как только мы выбираем страну, срабатывает событие onchange выпадающего списка, и Ajax.Updater обновляет комбобокс городов. Все! По-моему, все выглядит просто и красиво.

Комментариев нет: