(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

ASP.NET Dynamic Data - что может быть проще?

Источник: developers
Kartsev Evgeny

Введение

Новая библиотека Dynamic Data предназначена для быстрой генерации фронт-энда для баз данных SQL Server.

Ответ на вопрос - "Зачем мне это нужно?" будет рассмотрен в конце статьи.

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

Системные требования

  • Windows XP SP2/Vista/Server 2003/Server 2008
  • Visual Studio 2008 SP1 + .NET Framework 3.5 SP1
  • SQL Server 2005/2008

Следует сделать одно важное замечание - необходимо, чтобы был установлен пакет обновлений SP1 для Visual Studio 2008, иначе при выборе вида проекта не появится необходимой опции - создание ASP.NET Dynamic Data Web Site.

База данных

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

db structure

Рис 1. Схема тестовой базы данных

Существует список авторов (Authors) и издателей (Publishers). Также существует список книг (Books). Каждая книга может быть написана несколькими авторами, и принадлежать только одному издательству, у книги есть цена, которая не может быть менее 1 у.е. и более 1000 у.е., так же у книги есть дата публикации.

Итак, со схемой определились. Теперь приступим к созданию базы данных и наполнению ее тестовыми данными.

Открываем "SQL Server Management Studio" (Рис.2):

Open SSMS

Рис 2. Запуск "SQL Server Management Studio"

Жмем кнопку "New Query" (Рис 3):

new query

Рис 3. Создание нового запроса в SSMS

Копируем t-sql из листинга 1:

USE [master]
GO

IF EXISTS(SELECT 1 FROM sys.databases WHERE name='BookShop')
    DROP DATABASE [BookShop]

CREATE DATABASE [BookShop]
GO

USE [BookShop]
GO

CREATE TABLE [Authors]
(
    Id int IDENTITY(1,1) NOT NULL,
    Name nvarchar(150) NULL
)
GO

CREATE TABLE [Books]
(
    Id int IDENTITY(1,1) NOT NULL,
    Name nvarchar(250) NOT NULL,
    Description nvarchar(500) NULL,
    IdPublisher int NOT NULL,
    Price float NOT NULL,
    PublishedAt datetime NOT NULL
)
GO

CREATE TABLE [BooksToAuthors]
(
    IdBook int NOT NULL,
    IdAuthor int NOT NULL
)
GO

CREATE TABLE [Publishers]
(
    Id int IDENTITY(1,1) NOT NULL,
    Name nvarchar(150) NOT NULL
)
GO

ALTER TABLE Authors ADD CONSTRAINT PK_Authors
PRIMARY KEY (Id)
GO
ALTER TABLE Books ADD CONSTRAINT PK_Books
PRIMARY KEY (Id)
GO
ALTER TABLE BooksToAuthors ADD CONSTRAINT PK_BooksToAuthors
PRIMARY KEY (IdBook,IdAuthor)
GO
ALTER TABLE Publishers ADD CONSTRAINT PK_Publishers
PRIMARY KEY (Id)
GO

ALTER TABLE Books ADD CONSTRAINT FK_Books_Publishers
FOREIGN KEY (IdPublisher) REFERENCES Publishers(Id)
GO

ALTER TABLE BooksToAuthors ADD CONSTRAINT FK_BooksToAuthors_Books
FOREIGN KEY (IdBook) REFERENCES Books(Id)
GO

ALTER TABLE BooksToAuthors ADD CONSTRAINT FK_BooksToAuthors_Author
FOREIGN KEY (IdAuthor) REFERENCES Authors(Id)
GO

Листинг 1. Скрипт для создания схемы базы данных

Жмем кнопку "Execute" (Рис. 4):

Execute button

Рис 4. Запуск запроса

В результате в окне сообщения должны получить "Command(s) completed successfully".

Следующий шаг - заполнение только что созданной базы данных. Для этого выполняем запрос из листинга 2.

USE [BookShop]
GO

DELETE FROM BooksToAuthors
DELETE FROM Books
DELETE FROM Authors
DELETE FROM Publishers

INSERT INTO Authors (Name) VALUES ('Endrew Troelsen')
INSERT INTO Authors (Name) VALUES ('Matthew MacDonald')
INSERT INTO Authors (Name) VALUES ('Mario Szpuszta')
INSERT INTO Authors (Name) VALUES ('Eugene Kartsev')

INSERT INTO Publishers (Name) VALUES ('Apress')
INSERT INTO Publishers (Name) VALUES ('Williamspublishing')
INSERT INTO Publishers (Name) VALUES ('WROX')

INSERT INTO Books (Name, Description, IdPublisher, Price, PublishedAt)
VALUES ('Pro ASP.NET 3.5 in C#2008', '', (SELECT Id FROM Publishers WHERE Name='Apress'), '350.9900', GETDATE())
INSERT INTO Books (Name, Description, IdPublisher, Price, PublishedAt)
VALUES ('Another Interesting Book about .NET', '', (SELECT Id FROM Publishers WHERE Name='WROX'), '199.10', GETDATE())
INSERT INTO Books (Name, Description, IdPublisher, Price, PublishedAt)
VALUES ('Silverlight 2.0', '', (SELECT Id FROM Publishers WHERE Name='Williamspublishing'), '230.50', GETDATE())
INSERT INTO Books (Name, Description, IdPublisher, Price, PublishedAt)
VALUES ('Mu-mu, the beginning', '', (SELECT Id FROM Publishers WHERE Name='Apress'), '1.99', GETDATE())

INSERT INTO BooksToAuthors (IdBook, IdAuthor)
VALUES ((SELECT Id FROM Books WHERE Name='Pro ASP.NET 3.5 in C#2008'), (SELECT Id FROM Authors WHERE Name='Matthew MacDonald'))
INSERT INTO BooksToAuthors (IdBook, IdAuthor)
VALUES ((SELECT Id FROM Books WHERE Name='Pro ASP.NET 3.5 in C#2008'), (SELECT Id FROM Authors WHERE Name='Mario Szpuszta'))
INSERT INTO BooksToAuthors (IdBook, IdAuthor)
VALUES ((SELECT Id FROM Books WHERE Name='Another Interesting Book about .NET'), (SELECT Id FROM Authors WHERE Name='Endrew Troelsen'))
INSERT INTO BooksToAuthors (IdBook, IdAuthor)
VALUES ((SELECT Id FROM Books WHERE Name='Silverlight 2.0'), (SELECT Id FROM Authors WHERE Name='Matthew MacDonald'))
INSERT INTO BooksToAuthors (IdBook, IdAuthor)
VALUES ((SELECT Id FROM Books WHERE Name='Mu-mu, the beginning'), (SELECT Id FROM Authors WHERE Name='Eugene Kartsev'))

Листинг 2. Скрипт для наполнения БД тестовыми данными

Итак, тестовая база данных готова. Можно приступать к созданию сайта на основе ASP.NET Dynamic Data, но прежде следует сделать несколько замечаний.

Очень важно, чтобы структура базы данных была правильная (под правильностью понимается соответствие 3НФ). Данное требование особо остро ощущается при создании приложений на основании Dynamic Data или приложений, использующих автоматически сгенерированную схему классов LINQ2SQL. Но обо всем по порядку.

Основы ASP.NET Dynamic Data

Чтобы не тратить много времени на теорию, предлагаю начать с создания тестового приложения на базе Dynamic Data.

Запускаем Visual Studio 2008.

Выбираем File->New -> WebSite… (Рис 5):

new web site

Рис 5. Создание нового веб-сайта

Выбираем тип проекта "Dynamic Data Web Site" сохраняем его в папке "C:\TestApp\DynamicDataSite", если такой папки не существует, она будет создана (Рис 6):

_6 Dynamic Data Web Site

Рис 6. Создание проекта "Dynamic Data Web Site"

После описанных выше шагов будет создан веб-сайт со структурой как показано на рисунке 7:

Dynamic created web site

Рис 7. Структура проекта "ASP.NET Dynamic Data Web Site"

Следующий шаг - генерация классов LINQ2SQL на основании созданной базы данных.

Добавляем "LINQ to SQL Classes" с именем "BookShop.dbml" (рис 8-9):

8 Add new item

Рис 8. Добавление LINQ2SQL классов, шаг 1

Add linq 2 sql

Рис 9. Добавление LINQ2SQL классов, шаг 2

Visual Studio предложит сохранить сгенерированные классы в папке "App_Code" (Рис 10). Согласимся с этим - жмем "Yes":

Save to App_Code folder

Рис 10. Сохранение файлов с C# кодом в папке "App_Code"

Далее будет открыт дизайнер LINQ2SQL, в который необходимо перенести таблицы из базы данных.

Открываем "Server Explorer" в Visual Studio и добавляем новый коннекшн к тестовой базе данных (Рис 11):

Server Explorer

Рис 11. Добавление нового соединения к БД

Вписываем необходимые параметры в окне "Add Connection" и жмем кнопку "Test Connection" (Рис 12):

Add connection

Рис 12. Форма "Add Connection"

Результат тестирования коннекшна должно быть окно, показанное на рис. 13:

Connection Succeeded

Рис 13. Удачное тестирование соединения с БД

Если вы получили ошибку соединения - необходимо вернуться на шаг назад и проверить правильность ввода данных в окне "Add Connection".

После выполненных шагов переносим таблицы из базы данных "BookShop" на форму дизайнера LINQ2SQL (Рис 14):

BookShop tables

Рис 14. Перенос таблицы из "Server Explorer" на форму дизайнера "LINQ2SQL"

В результате генерирования сущностей LINQ2SQL дизайнер должен выглядить приблизительно так-же как и на рис. 15:

LINQ2SQL Entities

Рис 15. Сущности на дизайнере LINQ2SQL

Следующим шагом необходимо раскомментировать строку в файле "Global.asax":

model.RegisterContext(typeof(BookShopDataContext), new ContextConfiguration() { ScaffoldAllTables = true });

Хочу обратить ваше внимание, что необходимо подменить значение "YourDataContextType" на "BookShopDataContext", а также поменять значение параметра "ScaffoldAllTables" с false на true.

Параметр "ScaffoldAllTables" указывает на то, что необходимо взять все таблицы из модели LINQ2SQL и сгенерировать для них формы.

Вот и все, что нужно сделать для того, чтобы сайт работал - следующим шагом жмем кнопку "Save All", после чего жмем "F5" и тестируем работоспособность сайта (Рис 16-17):

Dynamic data default page

Рис 16. Тестирование сгенерированного веб-сайта

BooksToAuthors page

Рис 17. Тестирование сгенерированного веб-сайта

Сгенерированный веб-сайт - полностью работоспособное приложение, которое является "Лицом" базы данных и позволяет делать CRUD (Create, Read, Update, Delete) операции со всеми сущностями БД + простую выборку.

Вывод: Используя ASP.NET Dynamic Data можно, потратив 15 минут, создать полностью работоспособное приложение, не написав при этом ни строчки кода.

ASP.NET Dynamic Data deep dive

Модель программирования в Dynamic Data основана на атрибутах, поэтому прежде чем приступать к рассмотрению примера "кастомизации" веб-приложения, предлагаю ознакомиться со списком основных атрибутов в мире Dynamic Data. Исчерпывающая информация об атрибутах можно найти в блоге Maira Wenzel.

Описание атрибутов для классов (Сущностей):

Attribute name Description
TableName  Имя таблицы, которое будет использоваться в url, в списке таблиц на главной странице, а также в качестве имени в разделе.
DisplayColumn  Первый параметр атрибута - имя колонки, которое будет использоваться по умолчанию в DropDownList в других разделах на сайте (где есть Foreign Key на текущее свойство). Второй параметр - имя колонки, по которой должна осуществляться сортировка.
ScaffoldTable  True/False - отображает или скрывает таблицу. По умолчанию равен True.

Описание атрибутов для свойств (Property):

Attribute name Description
Required  Определяет является ли свойство обязательным. Необходимо для валидации. Если равно True, при создании новой записи поле не может быть пустым.
StringLength  Определяет длину строки, которое может быть введено в текстовое поле, сгенерированное для свойства. Нужен для валидации.
Description  Определяет текст, который будет появляться при наведении курсора на элемент управления (tooltip) в режиме редактирования.
DisplayName  Имя свойства, которое будет отображаться в качестве названия колонки (header) в List Mode, а также в качестве названия редактируемого поля в Edit Mode.
DefaultValue  Значение по умолчанию, которое будет использовано во-время создания новой записи.
RegularExpression  Определяет регулярное выражение, которое будет использовано для валидации значения введенного в элемент управления в режиме редактирования.
DataType  Тип данных в который будет конвертироваться значение введенное в элемент управления.
DisplayFormat  Формат, для отображения значения свойства в Display/List/Edit режимах.
Range  Определяет минимальное и максимальное значение, которое может быть введено в элемент управления.
ScaffoldColumn  Определяет, необходимо ли показывать/скрывать свойство.
UIHint  Определяем "кастомный" элемент управления для отображения данных свойства.

На рис.18 представлена схема взаимодействия сущностей с типами Metadata. В общей схеме есть сущности, которые были сгенерированы из базы данных (LINQ2SQL Entities). Так как сгенерированные сущности помечены как partial - это означает, то можно (и нужно) реализовать вторую часть класса.

В конечном итоге, необходимо сделать 2 шага:

1 - Реализовать класс Metadata, который будет содержать описание для необходимых свойств. Сигнатура свойств в классе Metadata должна быть идентичной сигнатуре свойств в классе, сгенерированном LINQ2SQL.

2 - Реазиловать вторую часть сгенерированного LINQ2SQL класса, помеченного как partial и добавить к нему атрибут MetadataType.

Metadata Scheme

Итак, продолжим рассмотрение примера…

Создаем 2 дополнительные папки с именами "BusinessObjects" и "Metadata" в папке "App_Code", в которых создаем файлы бизнес объектов и Metadata, как показано на рис. 19:

Add classes to project

Рис 19. Файлы бизнес объектов и мета-информации.

Для простоты примера опишем только сущность "Book", остальные пометим атрибутом [ScaffoldTable(false)] для того, чтобы не показывать эти таблицы на сайте.

Author.cs:

using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(AuthorMetadata))]
public partial class Author
{
}

AuthorMetadata.cs:

using System.ComponentModel.DataAnnotations;
[ScaffoldTable(false)]
public class AuthorMetadata
{
}

BooksToAuthor.cs:

using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(BooksToAuthorMetadata))]
public partial class BooksToAuthor
{
}

BooksToAuthorMetadata.cs:

using System.ComponentModel.DataAnnotations;
[ScaffoldTable(false)]
public class BooksToAuthorMetadata
{
}

Publisher.cs:

using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(PublisherMetadata))]
public partial class Publisher
{
}

PublisherMetadata.cs:

using System.ComponentModel.DataAnnotations;
[ScaffoldTable(false)]
public class PublisherMetadata
{
}

Итак, рассмотрим основные классы:

Book.cs:

using System.ComponentModel.DataAnnotations;

[MetadataType(typeof(BookMetadata))]
public partial class Book
{
}

BookMetadata.cs:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.Web.DynamicData;

[TableName("Книги")]
[DisplayColumn("Name", "Name")]
public class BookMetadata
{
    [Required(ErrorMessage = "Поле 'Имя книги' не может быть пустым")]
    [StringLength(250)]
    [Description("Официальное имя книги")]
    [DisplayName("Имя книги")]
    [DefaultValue("<Введите имя книги>")]
    [RegularExpression("(?!^<Введите имя книги>$).*", ErrorMessage = "Необходимо внести корректное название книги")]
    public string Name { get; set; }

    [Description("Краткое описание книги")]
    [DisplayName("Описание книги")]
    [DataType(DataType.MultilineText)]
    public string Description { get; set; }

    [Description("Цена книги")]
    [DisplayName("Цена")]
    [DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
    [Required(ErrorMessage = "Поле 'Цена' не может быть пустым")]
    [Range(1, 1000, ErrorMessage = "Значение должно быть в пределах 1-1000")]
    public double Price { get; set; }

    [Description("Издательский дом")]
    [DisplayName("Издатель")]
    public Publisher Publisher { get; set; }

    [ScaffoldColumn(false)]
    public EntitySet<BooksToAuthor> BooksToAuthors { get; set; }
}

Назначение атрибутов рассматривается на рисунке 20:
Attributes Mapping

Рис 21. Маппинг атрибутов

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

Чтобы решить эту проблему необходимо реализовать "TemplateField" элемент управления и добавить его в модель.

Сделаем это…

Добавляем 2 *.ascx элемента управления в папку "FieldTemplates" с именами "RichDateTime.ascx" и "RichDateTime_Edit.ascx" соответственно. Первый для отображения данных в List Mode, второй для отображения данных в Edit Mode (Рис 21):

Add ascx

Рис 21. Файлы для реализации "TemplateField"

Код в файлах:

RichDateTime.ascx:

<%@ Control Language="C#" AutoEventWireup="true"
    CodeFile="RichDateTime.ascx.cs"
    Inherits="DynamicData_FieldTemplates_RichDateTime" %>

<asp:Literal ID="dateTime" runat="server" Text="<%# FieldValueString %>" />

RichDateTime.ascx.cs:

using System.Web.DynamicData;
using System.Web.UI;

public partial class DynamicData_FieldTemplates_RichDateTime : FieldTemplateUserControl
{
    public override Control DataControl
    {
        get { return dateTime; }
    }
}

RichDateTime_Edit.ascx:

<%@ Control Language="C#" AutoEventWireup="true"
    CodeFile="RichDateTime_Edit.ascx.cs"
    Inherits="DynamicData_FieldTemplates_RichDateTime_Edit" %>

<asp:Calendar ID="calendar" runat="server" />

RichDateTime_Edit.ascx.cs:

using System.Web.DynamicData;
using System.Web.UI;

public partial class DynamicData_FieldTemplates_RichDateTime_Edit : FieldTemplateUserControl
{
    protected override void ExtractValues(System.Collections.Specialized.IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = ConvertEditedValue(calendar.SelectedDate.ToString());
    }

    public override Control DataControl
    {
        get
        {
            return calendar;
        }
    }
}

Далее добавляем новое свойство в класс "BookMetadata.cs" с нужными атрибутами:

[DisplayFormat(DataFormatString = "{0:mm-dd-yyyy}")]
[UIHint("RichDateTime")]
public DateTime PublishedAt { get; set; }

Сохраняем и запускаем сайт. Результат показан на рис. 22:

TemplateField Result

Рис 22. Результат применения TemplateField к свойству с типом DateTime

Как видим, добавление нового View в модель - невероятно простая задача.

Скачать исходный код примера.

Заключение

Итак, ответим на вопрос в начале статьи: "Зачем мне это нужно?" - ASP.NET Dynamic Data - гибкий фреймворк для очень быстрого построения FrontEnd для базы данных SQL Server. Для всех, кто хочет сохранить свое время - категорически рекомендую ознакомиться с этой технологией поближе, это позволит вам сэкономить несколько дней, а то и недель на разработку.

В ASP.NET Dynamic Data существует еще одно очень мощное и гибкое средство, которое называется "Dynamic Data Filtering". На момент написания статьи данное средство поставляется в виде отдельной сборки, которую можно бесплатно загрузить с сайта codeplex.com.

Как и все средства, Dynamic Data имеет ряд ограничений:

  • в качестве БД может быть только SQL Server 2005/2008
  • существует проблема при переходе из List в Edit Mode - т.к. на странице List.aspx используется UpdatePanel для AJAX запросов, в случае если существует достаточно большое количесвто данных - скажем, более 20 появится, пэйджер (Pager) для перехода к другим страницам в GridView. Проблема в том, что когда мы переходим в Edit Mode со страници List.aspx, и жмем на странице Edit.aspx линк "Update" или "Cancel" возвращаемся на страницу List.aspx, при этом Pager будет установлен на 1-ю страницу, а не на ту, которая была выбрана до перехода на Edit.aspx. Самый простой способ решения проблемы использование ListDetails.aspx вместо List.aspx -> Edit.aspx, при этом все действия будут осуществляться на одной странице и не будет потеряно значение в поле Pager.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 14.10.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
Microsoft Windows Professional 10, Электронный ключ
Microsoft 365 Business Basic (corporate)
Microsoft Office 365 Бизнес. Подписка на 1 рабочее место на 1 год
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Программирование на Visual С++
3D и виртуальная реальность. Все о Macromedia Flash MX.
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100