Россия, г. Санкт-Петербург |
Обеспечение взаимодействия Интернет-магазина с базой данных
Теперь переопределим обработчик события OnSelectedIndexChanged следующим образом:
protected void OnSelectedIndexChanged(object sender, EventArgs e) { if (GridViewProducts.SelectedValue != null) { DataClassesDataContext dcdc = new DataClassesDataContext( "Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True"); var query = dcdc.Products.Single(p => p.ProductID == Convert.ToInt32(GridViewProducts.SelectedValue)); ProductDetails.LoadContent(query); } }
Здесь мы применяем технологию LINQ для извлечения нужной нам записи из БД. Для этого мы создаем контекст базы данных DataClassesDataContext, после чего извлекаем из таблицы Products одну запись (метод Single ), причем такую, что ее ProductID равняется значению, сохраненному в выбранной строке. После этого вызовем метод LoadContent объекта ProductDetails.
Далее перепишем компонент ProductDetails. Его полный код можно посмотреть в приложенном примере, здесь мы рассмотрим только ключевые изменения.
Прежде всего, добавим в компонент ProductDetails компонент DataList, который будет отвечать за отображение комментариев пользователей о продукте. Компонент DataView работает с табличными данными, но в отличие от GridView, он отображает данные в виде списка, где для каждого элемента списка задан шаблон.
<asp:DataList ID="ReviewList" runat="server" Width="100%" CellPadding="0" CellSpacing="0"> <ItemTemplate> <asp:Label ID="Label1" style='font-weight:bold' Text='<%# DataBinder.Eval(Container.DataItem, "ReviewerName") %>' runat="server" /> <span class="Normal">говорит... </span> <asp:Image ID="Image1" runat="server" ImageUrl='<%# "~/images/ReviewRating" + DataBinder.Eval(Container.DataItem, "Rating") +".gif" %>' /> <br> <asp:Label ID="Label2" CssClass="Normal" Text='<%# DataBinder.Eval(Container.DataItem, "Comments") %>' runat="server" /> </ItemTemplate> <SeparatorTemplate> <br> </SeparatorTemplate> </asp:DataList>
Никаких новых атрибутов в теге DataList не определено. В теге ItemTemlate задается отображение каждой записи, код здесь идентичен коду в TemplateField компонента GridView ( DataBinder.Eval(Container.DataItem, "ReviewerName") является эквивалнтом более короткой записи Eval("ReviewerName") ). Тег SeparatorTemplate содержит описание разделителя между записями, в данном случае это пустая строка.
Теперь необходимо переписать метод LoadContent:
public void LoadContent(Product p) { Visible = true; Session["ViewProduct"] = p.ProductID; description.Text = (p.ProductModel != null && p.ProductModel.ProductModelProductDescriptionCultures.Count > 0) ? p.ProductModel.ProductModelProductDescriptionCultures[0].ProductDescription. Description : ""; UnitCost.Text = p.ListPrice.ToString("F2"); ModelName.Text = p.ProductModel != null ? p.ProductModel.Name : ""; ProductNumber.Text = p.ProductNumber.ToString(CultureInfo.InvariantCulture); ProductImage.ImageUrl = "~/products/images/ProductImages/" + p.ProductProductPhotos[0].ProductPhoto.LargePhotoFileName; ReviewList.DataSource = null; ReviewList.DataSource = p.ProductReviews; ReviewList.DataBind(); Comment.Text = ""; Email.Text = ""; Rating5.Checked = true; Name.Text = ""; }
Установим свойство Visible компонента в true, чтобы пользователи его смогли увидеть, после чего поместим идентификатор переданного объекта p в сессию, чтобы с ним можно было позднее работать. Далее обозначим все Label' ы компонента данными из описания товара, а также зададим свойство ImageUrl объекта ProductImage, чтобы отобразить изображение, соответствующее выбранному продукту. Для этого обратимся к коллекции ProductProductPhotos, возьмем первую запись (здесь надо проверить, что такая запись существует), далее по свойству ProductPhoto перейдем к записи в таблице "ProductPhoto" и получим имя файла с изображения из свойства LargePhotoFileName. При этом все изображения должны быть заранее помещены в папку ~/products/images/ProductImages/.
Последнее, что нам необходимо сделать – указать источник данных для ReviewList,и привязать его к этому источнику данных. В данном случае в качестве источника данных выступает список комментариев p.ProductReviews.
На рис. 11.8 показано, как будет выглядеть страница продукта при выбранном в таблице товаре.
Теперь продемонстрируем на примере комментариев, каким образом можно редактировать существующие и заносить в базу данных новые записи. Для этого доопределим компонент ProductDetails кодом, который будет содержать поля для создания комментария:
<tr> <td valign="top" width="100%"> <br> <asp:Label ID="Label3" runat="server" Text="Label" CssClass="SubContentHead">Добавление нового комментария:</asp:Label> <br> <br> <span class="NormalBold">Имя</span> <br> <asp:TextBox MaxLength="50" ID="Name" runat="server" /> <asp:RequiredFieldValidator ControlToValidate="Name" Display="Dynamic" Font-Names="verdana" Font-Size="9pt" ErrorMessage="Поле 'Имя' должно быть заполнено." runat="server" ID="RequiredFieldValidator1"></asp:RequiredFieldValidator> <br> <br> <span class="NormalBold">Почтовый адрес</span> <br> <asp:TextBox ID="Email" MaxLength="50" runat="server" /> <asp:RequiredFieldValidator ControlToValidate="Email" Display="Dynamic" Font-Names="verdana" Font-Size="9pt" ErrorMessage="Поле 'Почтовый адрес' должно быть заполнено." runat="server" ID="RequiredFieldValidator2"></asp:RequiredFieldValidator> <br> <br> <span class="NormalBold">Рейтинг</span> <br> <br> <asp:RadioButton ID="Rating5" Checked="true" GroupName="Rating" Text=" " runat="server" /> <asp:Image ID="ImageR5" runat="server" ImageUrl="~/Images/reviewrating5.gif" /> <br /> <asp:RadioButton ID="Rating4" Checked="false" GroupName="Rating" Text=" " runat="server" /> <asp:Image ID="ImageR4" runat="server" ImageUrl="~/Images/reviewrating4.gif" /> <br /> <asp:RadioButton ID="Rating3" Checked="false" GroupName="Rating" Text=" " runat="server" /> <asp:Image ID="ImageR3" runat="server" ImageUrl="~/Images/reviewrating3.gif" /> <br /> <asp:RadioButton ID="Rating2" Checked="false" GroupName="Rating" Text=" " runat="server" /> <asp:Image ID="ImageR2" runat="server" ImageUrl="~/Images/reviewrating2.gif" /> <br /> <asp:RadioButton ID="Rating1" Checked="false" GroupName="Rating" Text=" " runat="server" /> <asp:Image ID="ImageR1" runat="server" ImageUrl="~/Images/reviewrating1.gif" /> </td> </tr> <tr> <td> <br> <span class="NormalBold">Комментарий</span> <br> <asp:TextBox ID="Comment" TextMode="multiline" MaxLength="3850" Rows="7" Width="100%" runat="server" /> <asp:RequiredFieldValidator ControlToValidate="Comment" Display="Dynamic" Font-Names="verdana" Font-Size="9pt" ErrorMessage="Поле 'Комментарий' должно быть заполнено." runat="server" ID="RequiredFieldValidator3"></asp:RequiredFieldValidator> <br> </td> </tr> <tr> <td> <br> <asp:Button ID="AddReview" runat="server" OnClick="OnClick" Text="Добавить свой комментарий" /> <br> </td> </tr>
Этот код определяет TextBox, куда пользователь должен ввести свое имя, TextBox для указания адреса электронной почты, набор RadioButton' ов для указания рейтинга продукта, многострочный TextBox' а для комментариев (многострочность указывается атрибутом TextMode="multiline", а количество строк – Rows="7" ) и кнопки, которой будет задан серверный обработчик события OnClick. Здесь также стоит обратить внимание на то, что для каждого текстового поля задан компонент RequiredFieldValidator, который в случае, если текстовое поле пустое, а пользователь нажимает на кнопку (в общем случае – выполняет любое действие, которое приводит к postback' у) предотвращает отправление запроса на сервер и выводит сообщение об ошибке.
Теперь определим обработчик OnClick на сервере:
protected void OnClick(object sender, EventArgs e) { DataClassesDataContext dcdc = new DataClassesDataContext( "Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True"); var product = dcdc.Products.Single(p => p.ProductID == (int)Session["ViewProduct"]); ProductReview pr = new ProductReview(); pr.Comments = Comment.Text; pr.EmailAddress = Email.Text; if (Rating1.Checked) { pr.Rating = 1; } if (Rating2.Checked) { pr.Rating = 2; } if (Rating3.Checked) { pr.Rating = 3; } if (Rating4.Checked) { pr.Rating = 4; } if (Rating5.Checked) { pr.Rating = 5; } pr.ReviewDate = DateTime.Now; pr.ModifiedDate = DateTime.Now; pr.ReviewerName = Name.Text; product.ProductReviews.Add(pr); dcdc.SubmitChanges(); LoadContent(product); }
При помощи LINQ to SQL извлечем из базы данных продукт, с которым сейчас работаем, воспользовавшись тем, что мы храним идентификатор продукта в сессии. Создадим новый объект ProductReview, и означим свойства этого объекта данными, которые пользователь указал в соответствующих компонентах страницы (эти данные были переданы на сервер через ViewState и при восстановлении страницы ASP.NET записал эти значения в свойства соответствующих компонентов). Далее добавим созданный объект pr в коллекцию комментариев продукта – product.ProductReviews, и вызовем метод SubmitChanges контекста, который проанализирует изменения, сгенерирует и выполнит все необходимые запросы к базе данных.
Аналогичным образом можно реализовать редактирование и добавление любых сущностей. На рис. 11.9 представлен внешний вид карточки добавления комментария.
11.3. Ключевые термины
Microsoft Visual Studio, Server Explorer, База данных, Microsoft SQL Server 2008, DataSet, LINQ, LINQ to SQL.
11.4. Краткие итоги
Данное практическое занятие рассматривает следующие темы: