В первой части был разработан WCF сервис для получения данных из каталога веб-сайтов. Теперь приступим к разработке клиента.
Разработка клиентского приложения с применением технологии Ajax
Удаленный прокси для клиента
Для начала вернемся к странице WebsiteDataService.svc. Если открыть её в браузере и добавить в конец адреса параметр /jsdebug, то результатом будет вот такой JavaScript файл:
Type.registerNamespace('WebsiteCatalogService');
WebsiteCatalogService.IWebsiteDataService=function() {
WebsiteCatalogService.IWebsiteDataService.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
WebsiteCatalogService.IWebsiteDataService.prototype={
_get_path:function() {
var p = this.get_path();
if (p) return p;
else return WebsiteCatalogService.IWebsiteDataService._staticInstance.get_path();},
Insert:function(websiteInfo,succeededCallback, failedCallback, userContext) {
/// <param name="websiteInfo" type="WebsitesCatalog.WebsiteInfo">WebsitesCatalog.WebsiteInfo</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'Insert',false,{websiteInfo:websiteInfo},succeededCallback,failedCallback,userContext); },
Delete:function(id,succeededCallback, failedCallback, userContext) {
/// <param name="id" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'Delete',false,{id:id},succeededCallback,failedCallback,userContext); },
Get:function(id,succeededCallback, failedCallback, userContext) {
/// <param name="id" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'Get',false,{id:id},succeededCallback,failedCallback,userContext); }}
WebsiteCatalogService.IWebsiteDataService.registerClass('WebsiteCatalogService.IWebsiteDataService',Sys.Net.WebServiceProxy);
/// [ удалено для более краткого изложения ]
var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;
Type.registerNamespace('WebsitesCatalog');
if (typeof(WebsitesCatalog.WebsiteInfo) === 'undefined') {
WebsitesCatalog.WebsiteInfo=gtc("WebsiteInfo:http://schemas.datacontract.org/2004/07/WebsitesCatalog");
WebsitesCatalog..registerClass('WebsitesCatalog.WebsiteInfo');
}
Часть файла убрана для краткости. Даже если бегло посмотреть на данный код, то заметны знакомые название классов и методов. На самом деле это не что иное как Прокси для разработанного WCF сервиса.
Созданная автоматически реализация находится в указанном при описании контракта пространстве имен WebsiteCatalogService. Обратите внимание, что используется именно оно, а не пространство имен самого приложения. В качестве имени класса используется имя интерфейса IWebsiteDataService, который определяет контракт. Для хранения полученных данных зарегистрирован еще один класс – WebsiteInfo.
Таким образом, WCF сервис предоставляет возможность обращаться к нему с ASP.NET страниц.
JavaScript для взаимодействия с WCF
Перейдем к разработке клиентский части. В корневую папку проекта добавим новый файл WebsitesCatalog.js. Это и будет код взаимодействия с WCF сервисом. Его задачей является получение описания для заданного сайта. Давайте посмотрим на исходный код:
Type.registerNamespace("WebsitesCatalog");
WebsitesCatalog.WebsiteDetails = function (descriptionElementId) {
WebsitesCatalog.WebsiteDetails.initializeBase(this);
this._descriptionElement = document.getElementById(descriptionElementId);
}
WebsitesCatalog.WebsiteDetails.prototype =
{
ShowDescription: function (websiteId) {
WebsiteCatalogService.IWebsiteDataService.Get(websiteId,
Function.createDelegate(this, this.OnCompleted), this.OnError);
},
OnCompleted: function (result, userContext, methodName) {
if (result == "") { return; }
if (result.Id == "") { return; }
this._descriptionElement.innerHTML = result.Description;
},
OnError: function (result, userContext, methodName) {
var msg = result.get_exceptionType() + "\r\n";
msg += result.get_message() + "\r\n";
msg += result.get_stackTrace();
alert(msg);
}
}
WebsitesCatalog.WebsiteDetails.registerClass("WebsitesCatalog.WebsiteDetails");
Давайте детально рассмотрим созданный класс WebsiteDetails. Его конструктор получает Id элемента страницы, который будет использоваться для вывода описания выбранного сайта.
Функция ShowDescription() запрашивает с сервера описание сайта по заданному websiteId. Для этого она обращается к WCF сервису, используя метод Get(). Точнее сказать что запрос направляется Прокси, который уже пересылает его на сервер. При этом используется асинхронный подход к взаимодействию, поэтому в качестве параметров указаны функции, которые будут отвечать за обработку ответа.
Функция OnCompleted() при удачном завершении асинхронного вызова получит данные выбранного сайта. После этого проверяется значение Id и если оно не пустое (т.е. сайт был найден в базе), то описание сайта записывается в заданный элемент страницы.
В случае сбоя вызова будет вызвана функция OnError() для вывода сообщения об ошибке.
Создаем ASP.NET страницу
Осталось добавить веб-страницу с именем Default.aspx также в корневой каталог сайта. Чтобы не отвлекаться от основной задачи, упростим ее на сколько это возможно. Добавим все ссылки из базы в виде текста, а для описания выбранного сайта выделим область под ним.
Файл разметки будет выглядеть следующим образом:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebsitesCatalog.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Demo</title>
<script type="text/javascript">
var websiteCatalog = null;
function OnPageLoad() {
websiteCatalog = new WebsitesCatalog.WebsiteDetails('_websiteDescription');
}
</script>
</head>
<body onload="OnPageLoad();">
<form id="form1" runat="server">
<asp:ScriptManager ID="_scriptManager" runat="server">
<Services>
<asp:ServiceReference Path="~/Service/WebsiteDataService.svc" />
</Services>
<Scripts>
<asp:ScriptReference Path="~/WebsitesCatalog.js" ScriptMode="Auto" />
</Scripts>
</asp:ScriptManager>
<div>
Websites:<br />
<asp:Literal ID="_websitesList" runat="server" /><br />
Description:<br />
<asp:Label ID="_websiteDescription" runat="server" Text="" />
<hr />
<table>
<tr>
<td>Title:</td>
<td><asp:TextBox ID="_siteTitle" runat="server" /></td>
</tr>
<tr>
<td>Url:</td>
<td><asp:TextBox ID="_siteUrl" runat="server" /></td>
</tr>
<tr>
<td>Description:</td>
<td>
<asp:TextBox ID="_siteDescription" runat="server" TextMode="MultiLine" />
</td>
</tr>
</table>
<asp:Button ID="_btnAddSite" runat="server" Text="Add" OnClick="AddSite_Click" />
</div>
</form>
</body>
</html>
Обратите внимание как подключаются скрипты. Они указаны в элементе <asp:ScriptManager> в соответствующих разделах. В <Services> задаются скрипты сервиса, а в <Scripts> – пользовательские.
При загрузке страницы срабатывает скрипт, создающий экземпляр класса WebsiteDetails.
В остальном страница вполне обычная. Она содержит место для вывода списка сайтов _websitesList, выбранного описания _websiteDescription, а также таблицу для добавления нового сайта в базу.
Перейдем к C# коду этой страницы. В нем содержится 2 метода:
- В методе Page_Load() выводится список сайтов. Для каждого из низ создается ссылка "[info]", при нажатии на которую срабатывает JavaScript. Он обращается к экземпляру WebsiteDetails и запрашивает данные о сайте. В готовом HTML коде это будет выглядеть так:
<a href="http://andrey.moveax.ru">Andrey on .NET</a> | <a href="javascript:websiteCatalog.ShowDescription('eacd4df4-f2fd-430a-9a88-4f353d5d73ff');">[info]</a>
- Метод AddSite_Click() добавляет новый сайт в каталог. Метод упрощен. В нем отсутствуют проверки на корректность введенных данных.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebsitesCatalog
{
public partial class Default : System.Web.UI.Page
{
private readonly WebsiteDataSource _dataSource = new WebsiteDataSource();
protected void Page_Load(object sender, EventArgs e)
{
foreach (WebsiteInfo websiteInfo in this._dataSource.Items) {
this._websitesList.Text += string.Format(
"<a href=\"{0}\">{1}</a> | <a href=\"javascript:websiteCatalog.ShowDescription('{2}');\">[info]</a><br />",
websiteInfo.Url,
websiteInfo.Title,
websiteInfo.Id);
}
}
protected void AddSite_Click(object sender, EventArgs e)
{
WebsiteInfo websiteInfo = new WebsiteInfo() {
Id = Guid.NewGuid().ToString(),
Title = this._siteTitle.Text,
Url = this._siteUrl.Text,
Description = this._siteDescription.Text
};
this._dataSource.AddOrUpdate(websiteInfo);
Response.Redirect("/Default.aspx");
}
}
}
Проект завершен и готов к запуску.
И как всегда, в завершении ссылка на проект для Visual Studio 2010:
WebsitesCatalog.zip (16.6 kb).