четверг, 30 июня 2011 г.

MVC. Меню с закладками


Захотелось мне меню с закладками – как в стандартной заготовке MVC3 Web Application (Home - About), но чтоб активная вкладка выделять каким то образом – для простоты цветом. В обычном приложении Web Forms это можно сделать довольно несложно на серверной стороне. В приложении MVC3 для меня это оказалось непростой задачей. Поэтому буду ее решать.
Итак, у нас есть список, который и представляет собой меню:
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                </ul>

В CSS-файле есть раздел “TAB MENU”, в котором описывается стиль нашего меню, в том числе есть такое описание:
ul#menu li.selected a {
    background-color: #fff;
    color: #000;
}

Понятно, что описывается стиль активного элемента списка. Теперь надо динамически в зависимости от активной страницы устанавливать стиль selected соответствующему элементу списка. Создадим несложную модель нашего меню (листинг 1).
Листинг 1 – Модель меню
        public class TabMenu
        {
            private TabMenu(string text, string action, string controller)
            {
                Text = text;
                Action = action;
                Controller = controller;
            }

            public static TabMenu Create(string text, string action, string controller)
            {
                return new TabMenu(text, action, controller);
            }

            public string Text { get; private set; }
            public string Action { get; private set; }
            public string Controller { get; private set; }
        }

Как видно, закладка имеет свойства: текст, т.е. что будет написано не закладке, ссылка и контроллер.
Теперь создадим элемент управления HTML для представления (листинг 2).
Листинг 2Компонент для меню
    public static class MenuHtmlHelper
    {
        public static HtmlString TabbedMenu(this HtmlHelper helper, IEnumerable<TabMenu> tabs)
        {
            RouteData route = helper.ViewContext.RequestContext.RouteData;
            string controller = route.GetRequiredString("controller");
            string action = route.GetRequiredString("action");

            string menu = "<ul id=\"menu\">";
            foreach (var tab in tabs)
            {
                if (controller == tab.Controller && action == tab.Action)
                    menu += "<li class='selected'>" + helper.ActionLink(tab.Text, tab.Action,
                        tab.Controller) + "</li>";
                else
                    menu += "<li>" + helper.ActionLink(tab.Text,
                        tab.Action, tab.Controller) + "</li>";
            }
            menu += "</ul>";
            return MvcHtmlString.Create(menu);
        }
    }

Как видно, компонент получает список закладок меню. У той закладки, у которой контроллер и ссылка совпадают с контроллером и ссылкой текущей страницы устанавливается стиль selected для элемента списка. Не забудьте подключить пространства:
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

Теперь в файле _Layout.cshtml заменяем старое меню на новое (листинг 3).
Листинг 3 – Код меню в представлении
                @Html.TabbedMenu(new List<TabMenu> {

                    TabMenu.Create("Главная", "Index", "Home"),

                    TabMenu.Create("О проекте", "About", "Home")

                })

Чтоб представление «увидело» наш компонент надо подключить:
@using MvcApplication3.Models;

Практически, все. Результат на рисунке 1.
Рисунок 1 – Меню с закладками

Успехов!


четверг, 23 июня 2011 г.

MVC - часть2. Data Access Level


В прошлой статье мы сошлись на том, что получать данные в контроллере не  есть хорошо и правильно. И это так. С данными должна работать модель. Но обычно программисты выделяют еще один уровень, еще одну модель, которую будет использовать основная, - Data Access Level. То есть модель, которая непосредственно получает данные из БД, XML и других источников. И тут мы подходим к вопросу о технологии доступа к данным. Т.к. мы программируем в мире объектно-ориентированном, то и логичным было появление так называемых объектно-ориентированных технологий доступа к данным - object-relational mapping (ORM). Что это, какие виды и типы бывают, мы рассматривать не будем – оставляю это для самостоятельного изучения. В нашем же проекте мы будем использовать технологию ADO.NET Entity Framework (EF), являющейся ORM решением для .Net Framework от Microsoft.
Напомню, что у нас есть таблица в БД MS SQL Server с данными об объектах нашей карты (листинг 1).
Листинг 1 – Таблица объектов карты
CREATE TABLE [dbo].[Map] (
  [id] int IDENTITY(1, 1) NOT NULL,
  [ObjectName] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  [ObjectAddress] nvarchar(256) COLLATE Cyrillic_General_CI_AS NULL,
  [Longitude] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  [Latitude] nvarchar(128) COLLATE Cyrillic_General_CI_AS NULL,
  PRIMARY KEY CLUSTERED ([id])
)

Создадим Data Entity модель по этой таблице. Для это щелкаем правой кнопкой на папке Model нашего проекта, выбираем там AddNew Item, видим диалоговое окно как на рисунке 1.

                            Рисунок 1 – Добавление нового элемента в проект

В этом диалоге выбираем раздел Data – ADO.NET Entity Data Model, называем нашу модель (например, MapDataModel). Далее студия запускает визард, который помогает нам правильно сформировать модель. Идем по порядку: Choose Model Contents – Generate from database, Choose Your Data Connection – вносите настройки коннекта к вашей БД, Choose Your Database Objects – Tables (Map). Все, готово! Через пару секунд студия сгенерирует нам модель доступа к данным (рисунок 2).
Рисунок 2 – Модель данных

У нас данные хранятся крайне просто. Если не сказать примитивно. Если же данные образуют сложную систему с внешними ключами и прочим, то студия это все поймет и сгенерирует соответствующее количество классов с соответствующими связями. Если мы заглянем в файл MapDataModel.Designer.cs, то увидим содержимое рисунка 3.
Рисунок 3 – Классы, описывающие данные

Теперь как с этим работать. Давайте создадим модель DataManager (Model – Add –Class - DataManager). Создадим конструктор (листинг 2).
Листинг 2Конструктор DataManager
        private ProjectEntities _db;

        public DataManager()
        {
            _db = new ProjectEntities();
        }

Тут все понятно – мы создаем соединение к нашей БД, чтоб через него брать данные. Далее нам нужно сформировать лист, который будет отдаваться клиенту (листинг 3).
Листинг 3 – Формирование списка объектов для карты
        public List<object> GetListObjectMap()
        {
            List<object> list = new List<object>();

            foreach (Map item in _db.Map)
            {
                var data = new
                {
                    Name = item.ObjectName,
                    Latitude = item.Latitude,   //широта
                    Longitude = item.Longitude, //долгота
                    Html = item.ObjectAddress,
                };
                list.Add(data);
            }
            return list;
        }

Тут тоже понятно – каждую запись из таблицы БД в цикле мы добавляем в список.
Теперь идем в контроллер и очищаем метод ObjectToMap(). Создадим в нем экземпляр нашего DataManager и сформированный лист отдадим клиенту (листинг 4).
Листинг 4 – Обновленный метод контроллера ObjectToMap()
        [AcceptVerbs(HttpVerbs.Get)]
        public JsonResult ObjectToMap()
        {
            DataManager dm = new DataManager();

            return Json(dm.GetListObjectMap(), JsonRequestBehavior.AllowGet);
        }

Рисунок 4 – Результат работы

Просто? Вот так красиво и удобно.
Для лучшего понимания того, как и что делать, рекомендую изучать видеоподкасты на сайте . В них с комментариями описываются основные приемы работы от самых простых к сложным.
В следующей статье мы рассмотрим, как можно редактировать объекты нашей карты.

понедельник, 20 июня 2011 г.

MVC - часть1. Загрузка данных


Итак, у нас те же исходные данные, что и раньше. Но приложение будем строить ASP.Net MVC 3 Web Application. Студия предложит выбрать шаблон приложения, я выбираю Internet Application и движок Razor (позднее обсудим, почему именно так стоит делать). Теперь у нас есть заготовка нашего проекта. В этой заготовке есть лишние элементы (они особо не помешают, но и место занимать ни к чему), поэтому я удаляю: контроллер AccountController.cs, модель AccountModels.cs, удаляю папку Account во View, а также файлы _LogOnPartial.cshtml и Error.cshtml. Этого будет достаточно для начала работы. При запуске проекта студия поругается на файл _Layout.cshtml на строку:
            <div id="logindisplay">
                @Html.Partial("_LogOnPartial")
            </div>
это произошло из-за того, что представление, на которое тут идет ссылка, мы удалили. Заменим ссылку на тег <br />. Запустим снова проект – все работает.
Для начала создадим саму карту. Открываем Index.cshtml и убираем все кроме верхних трех строк. Ниже вставляем известный по прошлым статьям код (листинг 1).
Листинг 1 – Загрузка карты
    <script src="http://api-maps.yandex.ru/1.1/index.xml?key=”тут ваш ключ
        type="text/javascript">
    </script>

    <script type="text/javascript" src="../../Scripts/jquery-1.5.1.min.js">
    </script>

    <script type="text/javascript" >
        var group = [];
        var map;

        $(document).ready(function() {
            // Найдем на странице html элемент в котором будет размещаться карта
            map = new YMaps.Map(YMaps.jQuery("#YMapsID")[0]);

            map.setCenter(new YMaps.GeoPoint(55.975669, 54.732613), 10);

            // добавим контролы
            map.addControl(new YMaps.ToolBar());
            map.addControl(new YMaps.Zoom());
            map.addControl(new YMaps.TypeControl());
            map.enableScrollZoom();

            var searchControl = new YMaps.SearchControl({
                resultsPerPage: 2,  // Количество объектов на странице
                useMapBounds: 1     // Объекты, найденные в видимой области карты
                // будут показаны в начале списка
            });
            map.addControl(searchControl);
        });

    </script>

    <div id="YMapsID" style="width: 100%; height: 600px; padding-left: 0px; ">
    </div>

Запускаем проект и видим пустую карту. Это готово, хорошо. Теперь нам нужны данные на этой карте. В самой первой статье для загрузки мы пользовались HttpHandler’ом. В архитектуре MVC им пользоваться не с руки по архитектурным соображениям, поэтому попробуем загрузить данные в методе контроллера. Для этого откроем наш HomeController и добавим ему метод, как в листинге 2.
Листинг 2 – Получение данных
        [AcceptVerbs(HttpVerbs.Get)]
        public JsonResult ObjectToMap()
        {
            List<object> list = new List<object>();

            string connString = ConfigurationManager.ConnectionStrings["project"].ConnectionString;
            using (SqlConnection con = new SqlConnection(connString))
            {
                string sql = "exec dbo.p_Get_MyObject_Info ";

                SqlCommand cmd = new SqlCommand(sql, con);
                con.Open();
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var data = new
                        {
                            Name = reader.GetString(0),
                            Latitude = reader.GetString(2),  //широта
                            Longitude = reader.GetString(1), //долгота
                        };
                        list.Add(data);
                    }
                }
                con.Close();
            }
           
            return Json(list, JsonRequestBehavior.AllowGet);
        }

Как видно из этого листинга, код почти идентичен коду HttpHandler'а. Есть одно отличие – перед телом метода указан атрибут [AcceptVerbs(HttpVerbs.Get)]. Он отвечает за то, что метод будет обрабатывать http-запрос Get, в то время как Post у нас пока не реализован. Не забудьте в Web.Config добавить строку соединения project и реализовать процедуру, формирующую данные для карты dbo.p_Get_MyObject_Info. Хорошо. Теперь мы должны получить данные на стороне клиента и отобразить их.
Для этого в представлении Index к jQuery коду добавим функцию из листинга 3.
Листинг 3 – Получение данных на клиенте
            function getObject() {
            $.getJSON("/Home/ObjectToMap", null, function (mapObj) {
                // Группа объектов
                group["new"] = new YMaps.GeoObjectCollection("default#");

                $.each(mapObj, function (i) {
                    var point = new YMaps.GeoPoint(this.Longitude, this.Latitude);
                    var placemark = new YMaps.Placemark(point);
                    placemark.name = this.Name;

                    //тут засовываем html
                    placemark.description = this.Html + "<br />";

                    group["new"].add(placemark);
                });

                map.addOverlay(group["new"]);
            });
        };

Тут мы обращаемся к методу контроллера, который реализовали раньше и в цикле грузим данные на карту. И, наконец, нужно вызвать эту функцию в конце функции $(document).ready. Запускаем проект и видим, что на карте отображны наши объекты.
Победа? Как бы не так! Оказывается, получение данных в контроллере не есть методологически верное решение. Исправлять ситуацию будем в следующей статье.
И, как всегда, принимаются пожелания, указания на недочеты и ошибки, вопросы.

вторник, 14 июня 2011 г.

MVC


Любой программист .Net (да и большинство других) слышал про загадочную аббревиатуру MVC. Что это такое?
Это архитектура приложения на ASP.Net, призванная устранить недостатки традиционной платформы. MVC – это modelviewcontroller или модель – представление – контроллер. Т.е. приложение, построенное на данной архитектуре, будет иметь три выраженные части – модель (моделирует предметную область, работает с данными), представление (отображает информацию, по сути, является интерфейсом приложения), контроллер (управляет реакцией модели и представления). Не претендую на стопроцентную корректность, но примерно так.
В чем же преимущество этой архитектуры? Не буду перечислять классику, с ней можно ознакомиться самостоятельно в интернете или книгах, скажу, что прельстило лично меня. Когда стал «проводить разведку» по данной технологии, слышал разные мнения – и уменьшает, «облегчает» html-код, и просто создан для работы на клиенте с jQuery, и прочая, прочая… А для меня самым важным оказалось то, что при помощи такой архитектуры в приложение вносилась довольно жесткая структуризация. А это позволяет повысить эффективность и разработки, и доработки, и поддержки, позволяет разделять ответственность, разрабатывать тесты к отдельным компонентам системы и многое другое. Короче, я за порядок и системность.
Естественно, что мне захотелось понять, как разрабатывать приложения с такой архитектурой, как все это работает и т.д. И я решил переделать «карточный» проект с модулем Yandex.Maps по-новой – с использованием MVC3. Далее я буду выкладывать статьи, где буду описывать свои «мытарства» по освоению данной технологии.