Introduction
This article walks you through creating a custom web part for SharePoint 2010 and renders Bing Map in it. Here are some of the few pre-requisites:
- You must be familiar with basic SharePoint 2010 functionality
- You must have Visual Studio 2010 with SharePoint extensions installed
- You must have read Sample WPF application consuming Bing Maps Web Services. This article will walk you through how to create a Bing Maps account and the basic services Bing Map exposes.
You should expect your web part to look something like this:
Using the Code
The code is divided into the following components:
BingMapsWebPart
- This is the actual web part that will hold code to invoke helper methods defined in BingMapHelper
(see below).- BingMapNav.ascx - This user control will provide navigation buttons for the map. The buttons include zoom in/out, pane left/right/top/bottom.
- BingMapHelper.cs - This class has helper methods that invoke Bing Map services.
In Visual Studio 2010, create a new Visual Web Part project. Alternately, you could just open the code that is in the attached zip file:
Visually, here is how the project is structured:
BingMapHelper.cs
We will start with the BingMapHelper.cs. This class exposes two methods, viz - GeocodeAddressGeocodeResponse
and GetImagery
. GeocodeAddressGeocodeResponse
is basically translating the English like address to GeocodeResponse
(longitudes and latitudes). The code is pretty simple. You create an instance of GeoCodeService
, set your credentials (the key that you get from your bing account), set the address as its filter and future Geocode()
method. Here is how the code looks like:
public static GeocodeResponse GeocodeAddressGeocodeResponse(string address)
{
GeocodeRequest geocodeRequest = new GeocodeRequest();
geocodeRequest.Credentials = new GeocodeService.Credentials();
geocodeRequest.Credentials.ApplicationId = key;
geocodeRequest.Query = address;
ConfidenceFilter[] filters = new ConfidenceFilter[1];
filters[0] = new ConfidenceFilter();
filters[0].MinimumConfidence = GeocodeService.Confidence.High;
GeocodeOptions geocodeOptions = new GeocodeOptions();
geocodeOptions.Filters = filters;
geocodeRequest.Options = geocodeOptions;
GeocodeServiceClient geocodeService = new GeocodeServiceClient();
GeocodeResponse geocodeResponse = geocodeService.Geocode(geocodeRequest);
return geocodeResponse;
}
Next, let's look at the GetImagry()
method. This method receives longitude, latitude and the zoom level and passes them as the param to ImageryService
. The concept of invoking the service is very similar to the ones mentioned above. You create an instance of ImageryService
, set credentials and then set the parameters of MapUriRequest
object. Here is how the code looks like:
static string GetImagery(double latitude, double longititude, int zoom)
{
MapUriRequest mapUriRequest =
new MapUriRequest();
mapUriRequest.Credentials = new ImageryService.Credentials();
mapUriRequest.Credentials.ApplicationId = key;
new ImageryService.Credentials();
mapUriRequest.Center = new ImageryService.Location();
mapUriRequest.Center.Latitude = latitude;
mapUriRequest.Center.Longitude = longititude;
new ImageryService.Location();
mapUriOptions.Style = MapStyle.AerialWithLabels;
mapUriOptions.ZoomLevel = zoom;
MapUriOptions mapUriOptions =
new MapUriOptions();MapStyle.AerialWithLabels;
mapUriOptions.ImageSize = new ImageryService.SizeOfint();
mapUriOptions.ImageSize.Height = 400;
mapUriOptions.ImageSize.Width = 500;
mapUriRequest.Options = mapUriOptions;
new ImageryService.SizeOfint();
ImageryServiceClient imageryService = new ImageryServiceClient();
MapUriResponse mapUriResponse = imageryService.GetMapUri(mapUriRequest);
return mapUriResponse.Uri;
}
BingMapWebPart
Once you have this class ready, you are all set to create your web part. In order to do that, just right click the project -> Add new item -> Visual web part. Name this as BingMapsWebPart
.
The HTML part of the web part is very simple. Just a label, text box and button to make a call to maps service. Below that, we have an image which will render the map and below that, we have the user control for the navigation:
<asp:updatepanel runat="server">
<ContentTemplate>
<table width="100%" height="500px">
<tr height="30px" valign="top">
<td align="center" height="30px">Enter address
<asp:textbox runat="server" ID="txtAddress" ValidationGroup="BingMapsWP"
Width="300px">Space Needle, WA</asp:textbox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"
ControlToValidate="txtAddress" ErrorMessage="* Required"
ValidationGroup="BingMapsWP"></asp:RequiredFieldValidator>
</td>
</tr>
<tr height="40px" valign="top">
<td align="center" height="30px">
<asp:Button ID="btnGetMap" runat="server" Text="Get Maps"
ValidationGroup="BingMapsWP" onclick="btnGetMap_Click" />
<br />
<asp:Label ID="lblError" runat="server" Text=""></asp:Label>
</td>
</tr>
<tr>
<TD align="center">
<asp:image runat="server" ID="mapImage" Visible="False"></asp:image>
</TD>
</tr>
<tr height="30px">
<td align="center">
<uc1:BingMapNav ID="BingMapNav1" runat="server" />
</td>
</tr>
</table>
</ContentTemplate>
</asp:updatepanel>
Notice how we have kept the entire table inside an update patel. This helps in improving the user experience a bit by not flicking the page when you do any action (click of button).
The code behind of this control is very simple. There are mainly three sections:
- Code that runs click of button and invokes help method to get map: Here is how the code looks like:
protected void btnGetMap_Click(object sender, EventArgs e)
{
lblError.Visible = false;
try
{
GeocodeResponse resp =
BingMapHelper.GeocodeAddressGeocodeResponse(txtAddress.Text);
latitude = resp.Results[0].Locations[0].Latitude;
longititude = resp.Results[0].Locations[0].Longitude;
mapImage.ImageUrl = BingMapHelper.GetImagery(latitude, longititude, zoom);
mapImage.Visible = true;
}
catch (Exception ex)
{
lblError.Visible = true;
lblError.Text = ex.Message + "<BR>" + ex.StackTrace;
}
}
- Code that gets/sets the longititude, latitude and zoom level in view state.
- Code that does some action when the navigation buttons are clicked: In order to get the required zoom in or out, or pane effect, we will store the current longitude, latitude and zoom level in view state, whenever user clicks a button, we will update one of the three properties. E.g. if user clicks on scroll left button, we will reduce the longititude by 0.2.
BingMapNav.ascx
This is a little control that helps in nevigation. This is nothing but 6 buttons for zoom in/out, and panning to left/right/top/bottom. These buttons would just fire an event and the master user control would do operations based on the click.
Deployment
VSTS 2010 makes deployment of a SharePoint object a breeze. All you have to do is right click the project in solution explorer and click "Deploy".
No more painful way of authoring XML files and using 10 commands to install a simple web part. VSTS does it for you.
But there is one thing that you will have to do, that is to specify the WCF settings in the web.config of your web app. In order to locate the web.config, you should go to IIS, select the web app you want to update and right click -> Explore. Once there, you will see web.config. Add the following section into the web.config under configuration->system.ServiceModel
.
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IGeocodeService"
closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288"
maxReceivedMessageSize="65536" messageEncoding="Text"
textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384" maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm="">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
<binding name="BasicHttpBinding_IImageryService"
closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard" maxBufferSize="65536"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8"
transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384" maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm="">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address=http://dev.virtualearth.net/webservices/v1/geocodeservice/
GeocodeService.svc">http://dev.virtualearth.net/webservices/v1/
geocodeservice/GeocodeService.svc binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IGeocodeService"
contract="GeocodeService.IGeocodeService"
name="BasicHttpBinding_IGeocodeService" />
<endpoint address=</a />"
Testing
Once you have the web part deployed, go to your SharePoint site's page where you want to add this web part. Once there, click on edit page and select your web part from the web part gallery.
Note: New web part will appear under "Custom" category. Once you have added this web part, just play with it by entering various addresses and using the navigation buttons.
Points of Interest
I took a while to figure out how to let my webpart konw how to read the WCF configuration. Finally, I ended up keeping it in web.config of the web app. Do let me know if you think there is a better place to keep it.
The panning logic is a little off. When you hit the pane left/right/top/bottom, I just update the co-ordinates by 0.2. I should have considered zoom level by offseting the co-ordinates, but I would leave that to you.
The last thing I want to point out is that this is a very primitive looking web part. It does not have drag and drop feature, nor can it change between street view and it cannot do dozens of other things I want it to do. You are more than welcome to extend it.
History
- 17th May, 2010: Initial post