В этом руководстве мы рассмотрим, как создать пользовательский серверный элемент управления ASP.NET AJAX в качестве оболочки для JavaScript API Карт Google. Код на стороне сервера будет написан на C # (что я очень рекомендую), но его также легко можно написать на VB.NET. Основное внимание будет уделено созданию элемента управления, и мы подробно рассмотрим весь процесс создания настраиваемого серверного элемента управления ASP.NET AJAX, а также функциональности на стороне клиента.
Начиная
Если у вас еще не установлена Visual Studio, вам нужно взять последнюю экспресс-версию .
Как только это будет сделано, запустите его и перейдите в File -> New -> Project. В списке слева выберите «Веб», а затем выберите «ASP.NET AJAX Server Control» на главной панели справа. Назовите проект MapControl
и убедитесь, что выбран вариант создания нового решения (если применимо). Нажмите OK, чтобы создать проект.
Посмотрев в Solution Explorer, вы заметите, что Visual Studio уже сгенерировала некоторые файлы для нас. Мы немного разберем сгенерированный код, но перед этим давайте переименуем файлы и классы, содержащиеся в файлах.
- Переименуйте ClientControl1.js в GoogleMap.js
- Переименуйте ClientControl1.resx в GoogleMap.resx
- Переименуйте ServerControl1.cs в GoogleMap.cs
Теперь нажмите Control + H, чтобы открыть окно быстрой замены. Выберите соответствующие параметры, чтобы заменить ServerControl1
на GoogleMap
. Убедитесь, что он настроен на просмотр всего проекта, а не только текущего файла, а затем нажмите «Заменить все». Теперь сделайте то же самое, чтобы заменить ClientControl1
на GoogleMap
.
GoogleMap.js — на стороне клиента
Давайте разберем GoogleMap.js
по частям.
1
|
<reference name=»MicrosoftAjax.js»/>
|
Эта строка просто сообщает движку IntelliSense Visual Studio о включении типов и методов, содержащихся в MicrosoftAjax.js
в раскрывающиеся списки IntelliSense.
1
2
3
4
5
|
Type.registerNamespace(«MapControl»);
MapControl.GoogleMap = function(element) {
MapControl.GoogleMap.initializeBase(this, [element]);
}
|
Первая строка регистрирует пространство имен MapControl
с каркасом AJAX. Остальная часть кода здесь действует как конструктор для клиентского класса нашего пользовательского элемента управления. Здесь мы объявим все частные свойства на стороне клиента.
01
02
03
04
05
06
07
08
09
10
11
|
MapControl.GoogleMap.prototype = {
initialize: function() {
MapControl.GoogleMap.callBaseMethod(this, ‘initialize’);
// Add custom initialization here
},
dispose: function() {
//Add custom dispose actions here
MapControl.GoogleMap.callBaseMethod(this, ‘dispose’);
}
}
|
Здесь класс клиента нашего пользовательского элемента управления определяется с использованием модели прототипа. Это где все методы для класса клиента объявлены. Как вы, вероятно, догадались, методы initialize
и dispose
вызываются автоматически при создании и уничтожении экземпляра этого класса соответственно. В этом случае мы будем использовать метод initialize
для вызова API Карт Google и настройки карты.
1
2
3
|
MapControl.GoogleMap.registerClass(‘MapControl.GoogleMap’, Sys.UI.Control);
if (typeof(Sys) !== ‘undefined’) Sys.Application.notifyScriptLoaded();
|
Первая часть здесь регистрирует класс с определенным именем класса и пространством имен, а также назначает базовый класс (Sys.UI.Control). Наконец, вызывается метод Sys.Application.notifyScriptLoaded (), который уведомляет платформу Microsoft AJAX о завершении загрузки скрипта. Обратите внимание, что это больше не требуется в .NET Framework 4 и выше.
GoogleMap.cs — серверный код
Файл с именем GoogleMap.cs
содержит весь код на стороне сервера. Открыв файл, первое, что вы заметите, это то, что он содержит класс GoogleMap
, который наследуется от класса ScriptControl
. ScriptControl
— это абстрактный базовый класс, который наследуется от WebControl
и реализует IScriptControl
.
1
|
public class GoogleMap : ScriptControl
|
Хотя было бы хорошо оставить все как есть, мы можем создать более гибкую ситуацию, напрямую внедрив IScriptControl
и вместо этого унаследовав от WebControl
. Тем самым мы открываем возможность наследования от более сложного базового класса, такого как ListControl
. Я также столкнулся с проблемами, унаследованными от ScriptControl
при различных обстоятельствах. Давайте изменим это сейчас на следующее:
1
|
public class GoogleMap : WebControl, IScriptControl
|
GetScriptDescriptors
конструктор, вы увидите методы GetScriptDescriptors
и GetScriptReferences
.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
protected override IEnumerable<ScriptDescriptor>
GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(«MapControl.GoogleMap», this.ClientID);
yield return descriptor;
}
// Generate the script reference
protected override IEnumerable<ScriptReference>
GetScriptReferences()
{
yield return new ScriptReference(«MapControl.GoogleMap.js», this.GetType().Assembly.FullName);
}
|
Поскольку мы реализуем
IScriptControl
напрямую, модификаторы уровня доступа необходимо будет изменить с защищенного на общедоступный, а модификаторы переопределения следует полностью удалить.
В методе ScriptControlDescriptor
созданный и возвращенный объект ScriptControlDescriptor
сообщает платформе, какой клиентский класс создать, а также передает идентификатор элемента HTML, связанного с текущим экземпляром элемента управления. Как вы увидите в следующем разделе, это также механизм, посредством которого значения свойств передаются из кода на стороне сервера в класс клиента.
Код в методе GetScriptReferences
просто добавляет ScriptReference
к нашему клиентскому файлу кода — он будет автоматически загружен платформой при необходимости.
Определение свойств
Хорошо, теперь, когда у нас есть некоторая справочная информация, пришло время начать строить элемент управления карты. Для начала, мы добавим некоторые свойства к классу на стороне сервера (в GoogleMap.cs
), придерживаясь пока только GoogleMap.cs
. Масштаб и центральная точка карты — это то, что приходит на ум как необходимые свойства.
- Увеличить
- CenterLatitude
- CenterLongitude
1
2
3
4
5
6
7
8
9
|
private int _Zoom = 8;
public int Zoom
{
get { return this._Zoom;
set { this._Zoom = value;
}
public double CenterLatitude { get;
public double CenterLongitude { get;
|
Вы можете быть удивлены, как значения этих свойств, определенных в серверном классе, окажутся в клиентском классе. Ну, вот где ScriptControlDescriptor
вступает в игру. Просто вызывая метод AddProperty
объекта ScriptControlDescriptor
и передавая имя и текущее значение свойства на стороне клиента, платформа заботится обо всех деталях.
1
2
3
4
5
6
7
8
|
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(«MapControl.GoogleMap», this.ClientID);
descriptor.AddProperty(«zoom», this.Zoom);
descriptor.AddProperty(«centerLatitude», this.CenterLatitude);
descriptor.AddProperty(«centerLongitude», this.CenterLongitude);
yield return descriptor;
}
|
Теперь нам нужно определить свойства в клиентском классе. Откройте GoogleMap.js
и измените конструктор, чтобы он выглядел следующим образом:
1
2
3
4
5
6
7
|
MapControl.GoogleMap = function(element) {
MapControl.GoogleMap.initializeBase(this, [element]);
this._zoom = null;
this._centerLatitude = null;
this._centerLongitude = null;
}
|
Чтобы сделать эти свойства доступными для среды ASP.NET AJAX, нам нужно определить методы доступа get и set. Эти методы доступа должны следовать соглашениям об именах платформы — например, для свойства zoom
get_zoom
доступа должны называться get_zoom
и set_zoom
. Добавьте следующий код в объявление прототипа для класса:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
get_zoom: function() {
return this._zoom;
},
set_zoom: function(value) {
if (this._zoom !== value) {
this._zoom = value;
this.raisePropertyChanged(«zoom»);
}
},
get_centerLatitude: function() {
return this._centerLatitude;
},
set_centerLatitude: function(value) {
if (this._centerLatitude !== value) {
this._centerLatitude = value;
this.raisePropertyChanged(«centerLatitude»);
}
},
get_centerLongitude: function() {
return this._centerLongitude;
},
set_centerLongitude: function(value) {
if (this._centerLongitude !== value) {
this._centerLongitude = value;
this.raisePropertyChanged(«centerLongitude»);
}
}
|
Метод raisePropertyChanged
определен в классе-предке Sys.Component и вызывает событие propertyChanged
для указанного свойства.
Создание карты
Мы напишем код, который создает карту всего за минуту, но сначала нам нужно определить свойство, которое будет хранить объект карты. Таким образом, мы сможем получить доступ к карте после ее создания — например, в обработчике событий. Добавьте следующее объявление свойства в конструктор для класса клиента ( GoogleMap.js
) после других свойств:
1
|
this._mapObj = null;
|
Теперь давайте добавим функцию createMap
к прототипу:
1
2
3
4
5
6
7
8
9
|
createMap: function() {
var centerPoint = new google.maps.LatLng(this.get_centerLatitude(), this.get_centerLongitude());
var options = {
zoom: this.get_zoom(),
center: centerPoint,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
this._mapObj = new google.maps.Map(this._element, options);
}
|
Тип google.maps.LatLng
определен в JavaScript API Google Карт (на который мы будем ссылаться позже) и, как вы уже догадались, представляет точку на карте, определяемую широтой / долготой. В параметрах карты мы устанавливаем масштаб и центральную точку карты на значения, передаваемые платформой. Тип карты дорожной карты установлен, но это может быть легко установлено на спутник или местность.
Последняя строка создает объект google.maps.Map
, сохраняя ссылку на него в свойстве, которое мы создали выше.
Вы заметите, что конструктор принимает два параметра — прежде всего, первый — это ссылка на элемент HTML, связанный с элементом управления, — второй просто передает параметры карты.
Все, что осталось теперь на стороне клиента, — это вызвать нашу новую функцию createMap
из функции initialize, чтобы карта создавалась при инициализации createMap
управления.
1
2
3
4
5
|
initialize: function() {
MapControl.GoogleMap.callBaseMethod(this, ‘initialize’);
this.createMap();
},
|
Последние штрихи
Вернувшись к серверному коду ( GoogleMap.cs
), нам нужно переопределить свойство TagKey
в нашем классе GoogleMap
и вернуть значение HtmlTextWriterTag.Div
. Это обеспечит отображение элемента управления в виде элемента html div.
1
2
3
4
5
6
7
|
protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.Div;
}
}
|
Теперь давайте добавим приватное поле типа ScriptManager
в класс — назовем его sm
. Это сохранит ссылку на ScriptManager
страницы, который мы будем использовать чуть позже.
1
|
private ScriptManager sm;
|
Далее мы переопределим методы OnPreRender
и Render
класса GoogleMap
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
// Test for ScriptManager and register if it exists
sm = ScriptManager.GetCurrent(Page);
if (sm == null)
throw new HttpException(«A ScriptManager control must exist on the current page.»);
sm.RegisterScriptControl(this);
}
base.OnPreRender(e);
}
|
Здесь мы в основном просто получаем ScriptManager
текущей страницы (и проверяем, что он существует!), А затем регистрируем текущий экземпляр элемента управления с ним. Этот шаг, как и следующий, абсолютно необходим, иначе клиентская часть элемента управления не будет работать.
1
2
3
4
5
6
7
|
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
sm.RegisterScriptDescriptors(this);
base.Render(writer);
}
|
Это регистрирует дескрипторы скрипта ScriptManager
управления с помощью ScriptManager
страницы — sm
является ссылкой на ScriptManager
который мы OnPreRender
методе OnPreRender
.
И наконец (пока!), Мы сделаем этот элемент управления совместимым со сценариями частичного доверия, что довольно часто встречается из-за популярности общего веб-хостинга. В обозревателе решений откройте папку «Свойства», а затем откройте AssemblyInfo.cs
. Добавьте следующую ссылку в верхней части файла.
1
|
using System.Security;
|
Добавьте следующую строку где-то посередине или в нижней части файла.
1
|
[assembly: AllowPartiallyTrustedCallers()]
|
Тестирование элемента управления картой
Теперь мы готовы взять контроль на тест-драйв. Мы вернемся позже и добавим больше функциональности, но сейчас давайте создадим небольшой веб-сайт для тестирования элемента управления. Щелкните правой кнопкой мыши узел решения в обозревателе решений и выберите Добавить -> Новый веб-сайт. Убедитесь, что веб-сайт ASP.NET выбран из списка. Назовите веб-сайт MapControlTest
и нажмите ОК.
Во вновь созданном Default.aspx
добавьте ScriptManager
на страницу, прямо внутри элемента формы. Помните из последнего раздела, что это необходимо на странице, чтобы наш элемент управления GoogleMap
работал.
1
2
3
4
5
6
7
8
|
<body>
<form id=»form1″ runat=»server»>
<asp:ScriptManager runat=»server» ID=»ScriptManager1″>
</asp:ScriptManager>
<div>
</div>
</form>
</body>
|
Щелкните правой кнопкой мыши MapControlTest
веб-сайта MapControlTest
в Solution Explorer и выберите Add Reference из всплывающего меню. Перейдите на вкладку Projects, выберите MapControl и нажмите OK. Теперь нам нужно зарегистрировать сборку и пространство имен в Default.aspx
. Добавьте следующую строку в верхней части файла, чуть выше объявления DOCTYPE.
1
|
<%@ Register Namespace=»MapControl» TagPrefix=»mc» Assembly=»MapControl» %>
|
На этом этапе вы должны построить решение (Build -> Build Solution или клавишу F6), чтобы IntelliSense правильно работал для нового элемента управления. Теперь добавьте следующую декларацию управления на страницу.
1
2
|
<mc:GoogleMap runat=»server» CenterLatitude=»36.1658″ CenterLongitude=»-86.7844″ Width=»500″ Height=»500″>
</mc:GoogleMap>
|
Наконец, добавьте JavaScript API-интерфейс Google Maps (в верхней части страницы).
1
|
<script src=»http://maps.googleapis.com/maps/api/js?sensor=false»></script>
|
Теперь щелкните правой кнопкой мыши в редакторе (для Default.aspx
) и выберите «Просмотреть в браузере» во всплывающем меню. Ваш браузер должен открыть и загрузить страницу с картой в центре Нэшвилла, Теннесси. Вы можете поэкспериментировать со свойствами элемента управления, чтобы убедиться, что все работает правильно — в частности, попробуйте добавить свойство Zoom и посмотрите эффект на карте.
Добавление маркеров
Но простая карта не так интересна, поэтому в этом разделе мы добавим некоторые функции, которые позволяют отображать маркеры на карте.
Сначала нам понадобится класс, который содержит информацию о каждом маркере. Щелкните правой кнопкой мыши MapControl
проекта MapControl
в обозревателе решений и выберите «Добавить» -> «Класс» во всплывающем меню. Назовите класс MapMarker
и нажмите ОК.
Атрибут
Serializable
необходимо добавить в класс, чтобы он мог быть сериализован и передан в код на стороне клиента.
Добавьте свойства в класс, чтобы объявление класса выглядело так.
01
02
03
04
05
06
07
08
09
10
11
12
|
[Serializable]
public class MapMarker
{
public double Latitude { get;
public double Longitude { get;
public string Title { get;
public virtual string InfoWindowHtml { get;
public MapMarker()
{
}
}
|
Свойства Latitude
и Longitude
довольно очевидны — свойство « Title
предназначено для текста всплывающей подсказки, который будет отображаться при наведении InfoWindowHtml
мыши на маркер, а свойство InfoWindowHtml
будет содержать HTML- InfoWindowHtml
отображаемый во всплывающем окне при щелчке маркера.
В серверном классе GoogleMap
( GoogleMap.cs
) мы добавим свойство списка MapMarkers
. Атрибут PersistenceMode
указывает, что свойство сохраняется в серверном элементе управления ASP.NET как вложенный тег.
1
2
3
4
5
6
7
|
private List<MapMarker> markers = new List<MapMarker>();
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<MapMarker> Markers
{
get { return this.markers;
}
|
ParseChildren
также должен быть установлен в GoogleMap
класс для этого на работу.
1
2
|
[ParseChildren(true)]
public class GoogleMap : WebControl, IScriptControl
|
Теперь мы добавим другое свойство в ScriptControlDescriptor
в методе GetScriptDescriptors
для нового свойства.
1
2
3
4
5
6
7
8
9
|
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(«MapControl.GoogleMap», this.ClientID);
descriptor.AddProperty(«zoom», this.Zoom);
descriptor.AddProperty(«centerLatitude», this.CenterLatitude);
descriptor.AddProperty(«centerLongitude», this.CenterLongitude);
descriptor.AddProperty(«markers», this.Markers);
yield return descriptor;
}
|
Возвращаясь к клиентскому коду ( GoogleMap.js
), добавьте новый
объявление свойства с именем _markers
для конструктора. Пока мы
здесь, давайте также добавим свойство с именем _infoWindow
, которое будет
сохранить ссылку на всплывающее окно, которое появляется при нажатии на маркер.
01
02
03
04
05
06
07
08
09
10
11
|
MapControl.GoogleMap = function(element) {
MapControl.GoogleMap.initializeBase(this, [element]);
this._zoom = null;
this._centerLatitude = null;
this._centerLongitude = null;
this._markers = null;
this._mapObj = null;
this._infoWindow = null;
}
|
Теперь нам нужно добавить методы доступа get и set для свойства, как и раньше.
1
2
3
4
5
6
7
8
9
|
get_markers: function() {
return this._markers;
},
set_markers: function(value) {
if (this._markers !== value) {
this._markers = value;
this.raisePropertyChanged(«markers»);
}
},
|
Теперь осталось только добавить код, который будет отображать маркеры для функции createMap
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
createMap: function() {
var centerPoint = new google.maps.LatLng(this.get_centerLatitude(), this.get_centerLongitude());
var options = {
zoom: this.get_zoom(),
center: centerPoint,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
this._mapObj = new google.maps.Map(this._element, options);
var markers = this.get_markers();
if (markers != null) {
for (var i = 0; i < markers.length; i++) {
var marker = new google.maps.Marker
(
{
position: new google.maps.LatLng(markers[i].Latitude, markers[i].Longitude),
map: this._mapObj,
title: markers[i].Title
}
);
var that = this;
(function(marker, infoHtml) {
google.maps.event.addListener(marker, ‘click’, function() {
if (!that._infoWindow) {
that._infoWindow = new google.maps.InfoWindow();
}
that._infoWindow.setContent(infoHtml);
that._infoWindow.open(that._mapObj, marker);
});
})(marker, markers[i].InfoWindowHtml);
}
}
}
|
Это может показаться сложным, но на самом деле это не так — я разобью его на части и рассмотрю по частям.
1
2
3
|
var markers = this.get_markers();
if (markers != null) {
for (var i = 0; i < markers.length; i++) {
|
Здесь мы просто получаем массив маркеров, проверяем, что это не пустая ссылка, и перебираем массив.
1
2
3
4
5
6
7
8
|
var marker = new google.maps.Marker
(
{
position: new google.maps.LatLng(markers[i].Latitude, markers[i].Longitude),
map: this._mapObj,
title: markers[i].Title
}
);
|
Этот фрагмент кода — это то, что фактически добавляет маркер на карту — захват position
и title
маркера из массива с помощью markers[i]
и установка свойства map
для карты, созданной ранее.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
var that = this;
(function(marker, infoHtml) {
google.maps.event.addListener(marker, ‘click’, function() {
if (!that._infoWindow) {
that._infoWindow = new google.maps.InfoWindow();
}
that._infoWindow.setContent(infoHtml);
that._infoWindow.open(that._mapObj, marker);
});
})(marker, markers[i].InfoWindowHtml);
|
Первая строка — сохранить текущий контекст в обработчике событий — он больше не будет ссылаться на наш пользовательский элемент управления, поэтому мы используем его вместо этого. Поскольку мы выполняем итерацию по массиву, а переменные marker
и i
меняются, нам нужно передать переменные marker
и infoHtml
в новую самозапускающуюся анонимную функцию, чтобы при запуске обработчика события (который будет позже), переменные по-прежнему имеют свои правильные значения. Код внутри обработчика событий довольно прост — в основном это просто установка содержимого и открытие всплывающего информационного окна.
Теперь мы можем добавить маркеры на карту следующим образом (вам может понадобиться сначала создать решение, чтобы IntelliSense работал).
1
2
3
4
5
|
<mc:GoogleMap runat=»server» CenterLatitude=»36.1658″ CenterLongitude=»-86.7844″ Width=»500″ Height=»500″>
<Markers>
<mc:MapMarker Latitude=»36.1658″ Longitude=»-86.7844″ Title=»Nashville, TN» InfoWindowHtml=»<strong>Nashville, TN</strong>» />
</Markers>
</mc:GoogleMap>
|
Если маркеры будут размещены на карте через пользовательский ввод, возникнут проблемы с безопасностью — в частности, вам необходимо убедиться, что пользовательский ввод для свойства Title
закодирован в формате html, а входные данные для свойства InfoWindowHtml
не содержат каких-либо опасных код. Как всегда, безопасность в производственном приложении должна быть главной задачей.
Вывод
Надеемся, что в этом руководстве вы узнали не только о создании настраиваемого серверного серверного элемента управления ASP.NET AJAX с функциональностью на стороне клиента, но и немного об API Карт Google. Некоторые из вас могут подумать, что эта технология устарела, поскольку она применяется к веб-формам ASP.NET. Тем не менее, веб-формы все еще являются очень жизнеспособным вариантом для новой веб-разработки, особенно для отдельных разработчиков, работающих над короткими проектами и нуждающихся в быстром выполнении задач.
ASP.NET MVC, вероятно, лучше с точки зрения гибкости и тестируемости. Дело в том, что это руководство не защищает использование одной технологии над другой; он просто учит технике, которая может быть очень полезна при создании приложений ASP.NET Web Forms.