Vantage Point: Bob German's Weblog

Notes from BlueMetal Architects, where Bob is SharePoint Principal Architect. Here you will find postings on all things SharePoint, especially developer related topics.

Future Proof Solutions Part 2 - Developing SharePoint 2010 Solutions that become Provider Hosted Apps

Future Proof Solutions Part 2 - Developing SharePoint 2010 Solutions that become Provider Hosted Apps

Rate This
  • Comments 0

This article has accompanying code samples posted at on MSDN Code Gallery. The SharePoint 2010 solution is here, and its corresponding SharePoint 2013 app is here. The sample demonstrates how to develop code that works in SharePoint 2010 and also as a SharePoint 2013 App. The goal is to show you how to develop SharePoint web parts (and in this case, Event Receivers as well) that can be packaged as traditional solutions or as apps. Whether you’re ready for the new App model or not, it’s not too early to start developing in a new way  that works on premises or online, today or tomorrow.

This sample focuses on a Provider-hosted app that runs on an external web server or, with a little tweaking, in Windows Azure as an Auto-Hosted App. The same code can be packaged to run in SharePoint 2010 or 2013 as a traditional farm solution. Nearly 90% of the code is common between the SharePoint 2010 solution and the SharePoint 2013 app, providing a path to the future for those who aren’t quite ready for the new App model.

The sample also shows off the new Geolocation column that’s included in SharePoint 2013, and shows how to hold geographic locations using a text field in SharePoint 2010.

The Once and Future Bing 

Microsoft has sent a clear message to SharePoint developers everywhere: “we love you, but get your code off the SharePoint servers!” (See the article, The Maturing of SharePoint Development - A Perspective on the New App Model, for an architectural overview.) The new guidance is to put your code in the web browser, in Azure, on an external web site – anywhere except on the SharePoint servers themselves. This is a big departure from previous approaches, which have focused on server side code that runs directly within the SharePoint farm.

The first solution in this series ran in Javascript on the browser, and some of the code in this solution does that as well. However there are limitations to this approach. For example, if you want to run code away from the UI, such as an event receiver that runs in the background when SharePoint content changes, there’s no way to do that in the browser.

The solution uses Bing Maps to display a set of locations on a map. The locations are stored in a special list, which is based on a regular Contacts list, but which also contains geocoding information (mainly latitude and longitude). When a contact is added or updated, an event receiver uses the Bing Maps API to geocode the address, and stuffs the coordinates back into the list item for display. That is much more efficient than looking up each address every time the map is rendered, and it also allows some cool out-of-the-box SharePoint 2013 geocoding features to work in the App version.

The Locations List

Adding a list to SharePoint is pretty much the same in a traditional solution or a SharePoint 2013 App – except that the list ends up in the App Web in the latter case. That’s OK in this situation … the SharePoint 2013 App page simply redirects the user to the Locations list in the app web, where it looks like a perfectly normal list. (This might be the reason that Microsoft calls lists “Apps” in SharePoint 2013 – the user may not know or care that there’s a difference.)

The difference in this case is unique to geocoding. I wanted to use the cool new Geolocation column in the SharePoint 2013, yet it’s not available in SharePoint 2010. The SharePoint 2010 solution includes a site column, LocationSiteColumn, that’s a simple text field, and a Locations list that adds the LocationSiteColumn to a Contacts list.

Opening the SharePoint 2013 App in Visual studio, you might notice that there are two projects inside. The first, called simply “Locations”, is SharePoint hosted (like the previous example), and the second one, called “LocationsMap”, is Provider hosted and runs in an external web site. You could load the same code into an Auto-hosted app if you wanted to run it in Office 365’s special Azure implementation. The “Locations” project deploys a Geolocation site column and a Locations list to SharePoint. Except for the field type, the two are identical.

Listing 1 shows the LocationSiteColumn for SharePoint 2013; this is how to get a Geolocation column into SharePoint 2013. For some reason, there’s no way to do this via the SharePoint user interface. The SharePoint 2010 code is the same except it creates a “Text” column instead.

<?xmlversion="1.0"encoding="utf-8"?>
<Elementsxmlns=http://schemas.microsoft.com/sharepoint/>  
  <FieldID="{434bc332-b9c1-469d-818e-f6dff237e36e}"
  
       Name="GeoLocation"
 
       DisplayName="Geo Location"
 
       Type="Geolocation"
 
       Required="FALSE"
 
       Group="Location App">
  </Field>
</Elements>

Listing 1 – Defining a Geolocation site column in a SharePoint 2013 App

You’ll find a folder called “Geo” in the Locations project in the SharePoint 2010 solution, and in the LocationsWeb project in the SharePoint 2013 App. This folder contains a few classes for geocoding addresses and converting locations into JSON for use on the client side. The two versions are nearly identical – the only differing code is the SPGeoLocationField class, which saves a location into a text field in SharePoint 2010, or a Geolocation field in SharePoint 2013.

The classes are:

GeoLocation.cs – This file contains two classes – GeoLocations (which represents a set of locations to be mapped), and GeoLocation (which represents one of these locations.) Both classes are marked with the [DataContract] attribute, and their properties are decorated with [DataMember] attributes, making them serializable. Both classes are derived from SerializableObject, which knows how to translate them to JSON (and back to .NET objects if necessary).

GeoLocation also knows how to geocode an address, and contains the Bing Maps code to do that.

SerializableObject.cs – This class originated in my book, and is a class I’ve used over and over again ever since. It knows how to turn derived objects into JSON (JavaScript Object Notation), and then convert them back to .NET objects. This is not only useful in regular .NET code as an efficient way to serialize things to a string, it is also handy in Silverlight. And, since Javascript can natively read JSON, the serialized objects can be used there as well. In this case, the Geolocations will be placed on the web page for Javascript to read and plot on a map.

SPAccess.cs – This class checks a SharePoint list item (the one that fired the event receiver) to see if it needs to be geocoded, and if it does, it geocodes it. If you look inside, you might notice that the Locations list actually has an additional field, GeoCheck, which stores the most recently geocoded “query” (a composite of name, address, city, state, and country). This way, if a list item is modified in a way that doesn’t affect any of those fields, the code can see that the new query is the same as the old query in GeoCheck and skip geocoding it another time.

A key thing to notice about SPAccess is that it uses the .NET client OM rather than the server API. This works fine in the SharePoint server for the 2010 case (even on the server!), and in an external web site in the SharePoint 2013 App.

SPGeoLocationField.cs – This class gets and sets the geolocation field in the Locations list, and is the only geolocation code that is different between the two versions. In SharePoint 2013, it stores the latitude and longitude into the cool new SharePoint 2013 field; in SharePoint 2010, it saves values in the text field in JSON format.

There’s kind of a lot of code here to show all in the blog article, so please go download it from here and here to get the complete picture. The SPAccess class is especially interesting, however, so it’s shown below in Listing 2. This will give you an idea of what it’s like to code using the Client OM in .NET rather than in Javascript.

// CheckListItemGeoCode is called by the event receiver to ensure that a list item
// is correctly geocoded
public static void CheckListItemGeoCode(ClientContext clientContext, Guid listId,
                            int listItemId, string address, string city, string state, 
                            string country, object location, string geoCheck)
{
    // Build a Bing maps query from the address
    string query = Geo.GeoLocation.GetGeocodeQuery(address, city, state,
                           country);
 
    // Check to see if we have a new query
    // (different than last time we geocoded this item)
    if (!String.IsNullOrEmpty(query) &&
       (String.IsNullOrEmpty(geoCheck) || geoCheck != query))
    {
        // Yes - try to geocode this item
        if (clientContext != null)
        {
            // First, read the Bing Maps key from the web
            PropertyValues props = clientContext.Web.AllProperties;
            clientContext.Load(props);
            clientContext.ExecuteQuery();
            string bingMapsKey = props.FieldValues["BING_MAPS_KEY"] as string;
 
            // Use the Bing Maps web service to get the location
            // NOTE – the Bing Maps call is in the GeoLocation object constructor;
            // please download the full code sample to see how it works.
            location = Geo.SPGeoLocationField.GetFieldGeolocationValue
                       (new Geo.GeoLocation(query, bingMapsKey));
 
            // Now update the list item with the query (at least - so we don't
            // repeatedly attempt to geocode it), and the location (if we got one)
            List list = clientContext.Web.Lists.GetById(listId);
            Microsoft.SharePoint.Client.ListItem item = list.GetItemById(listItemId);
            if (location != null)
            {
                item["GeoLocation"] = location;
            }
            item["GeoCheck"] = query;
            item.Update();
 
            clientContext.ExecuteQuery();
        }
    }


// Build a Locations object containing all the locations in the list and put them
// on the page in JSON format so the client side can paint the map
public static void PlotLocations(ClientContext clientContext, string listName,
                                                  string showDashboard, string mapStyle,
                                                  Literal jsonDataControl)
{
    // Load location items
    List contactList = clientContext.Web.Lists.GetByTitle(listName);
    CamlQuery query = CamlQuery.CreateAllItemsQuery();
    Microsoft.SharePoint.Client.ListItemCollection items = contactList.GetItems(query);
    clientContext.Load(items);
 
    // Load Web properties (including Bing Maps key)
    PropertyValues props = clientContext.Web.AllProperties;
    clientContext.Load(props);
 
    clientContext.ExecuteQuery();
 
    GeoLocations locations = new GeoLocations();
 
    foreach (Microsoft.SharePoint.Client.ListItem li in items)
    {
        string title = li["Title"] as string;
        object spLocation = li["GeoLocation"];
 
        if (title != null && spLocation != null)
        {
           GeoLocation location = SPGeoLocationField.ParseLocationField(spLocation);
           locations.Items.Add(location);
        }
    }
 
    locations.Credentials = props.FieldValues["BING_MAPS_KEY"] as string;
    locations.MapStyle = mapStyle.ToLower();
    locations.ShowDashboard = showDashboard;
 
    jsonDataControl.Text = "<script language=\"javascript\" type=\"text/javascript\">" +
                                          "var locations = " + locations.Serialize() + ";" +
                                          "</script>";
}

Listing 2 - Methods to access geographic information in a SharePoint list

The CheckListItemGeoCode() method is called from the event receiver when a Locations list item changes. It builds a query for the Bing Maps geocoding service, and checks to see if the query has already been run (and thus the item already was geocoded). If the query is new, then it geocodes the address.

You may notice that the Bing Maps Key is stored in the SharePoint web property bag. The SharePoint 2013 App prompts the user for this key when it’s first installed; in SharePoint 2010 you’ll need to place this key into the web using SharePoint Designer.

Notice that the client OM calls are all synchronous – this makes development a breeze compared with the Javascript (or Silverlight) versions of the Client OM!

The PlotLocations() method is called from the web part code. Again, even though the SharePoint 2010 web part runs on the SharePoint server, the Client OM is used to allow easy porting of the code to a SharePoint 2013 app. The code loops through the list items, creating a Location object for each one that contains a GeoLocation. It assembles all these Location objects into a Locations object, and serializes the whole thing in JSON format into a literal control on the Visual Web Part (SharePoint 2010) or the page that will show up in the Client Web Part (in Sharepoint 2013).

The Event Receiver

SharePoint event receivers have always been a little wonky. They’re not guaranteed to run (though I've never had a problem personally), and their so-called “before” and “after” properties often don’t work as expected. Sometimes the list item contains the values instead of the “before” and “after” properties, and it often requires trial and error to get things working.

In the spirit of compatibility, Microsoft made sure to make the new SharePoint 2013 Remote Event Receivers similarly squirrely. Remote Event Receivers run in a SharePoint Provider Hosted app (in an external web site) or an Auto-hosted app (in Windows Azure with Office 365). When an item in SharePoint changes, SharePoint calls a web service in the external or Azure site; the web service can then use the Client OM or RESTful services to do any related work in SharePoint.

This example uses a regular event receiver in the SharePoint 2010 version and a remote event receiver in the SharePoint 2013 app. The trick to making it “future proof” is to use the Client OM in both cases – it turns out that the Client OM runs perfectly well in or out of the SharePoint server.

The SharePoint 2010 event receiver will look familiar to anyone who’s written an event receiver on the server, and is shown in Listing 3.

public class LocationsListEventReceiver : SPItemEventReceiver
{
    /// An item was added
    public override void ItemAdded(SPItemEventProperties properties)
    {
        base.ItemAdded(properties);
        CheckGeoCode(properties);
    }

    /// An item was updated
    public override void ItemUpdated(SPItemEventProperties properties)
    {
        base.ItemUpdated(properties);
        CheckGeoCode(properties);
    }
 
    private void CheckGeoCode(SPItemEventProperties properties)
    {
        Guid listId = properties.List.ID;
        int listItemId = properties.ListItemId;
        string address = properties.ListItem["WorkAddress"] as string;
        string city = properties.ListItem["WorkCity"] as string;
        string state = properties.ListItem["WorkState"] as string;
        string country = properties.ListItem["WorkCountry"] as string;
        object location = properties.ListItem["GeoLocation"] as string;
        string geoCheck = properties.ListItem["GeoCheck"] as string;

        using (ClientContext clientContext = new ClientContext(properties.WebUrl))
        {
            Geo.SPAccess.CheckListItemGeoCode(clientContext, listId, listItemId,|
                                      address, city, state, country, location, geoCheck);
        }
    }
}

Listing 3 – Server Side Event Receiver in SharePoint 2010

When an item is added or updated, the event receiver fires and calls CheckGeoCode. The “ed” endings on these events is a tip that these are asynchronous events – i.e. they fire at some point after the event occurs, so the SharePoint UI isn’t kept waiting while we make calls to the Bing Map web service. CheckGeoCode reads the list item values out of the event properties – note they are in properties.ListItem rather than the “After” properties as one might expect.

The Remote Event Receiver in the SharePoint 2013 app is divided into two parts. It is declared in the Locations project, and is installed in the App web along with the Locations List. However the code is found in the Locations Web project, which runs in an external web server, and can be found in the Services folder under LocationsEventReceiver.svc. When you add a remote event receiver to your project, Visual Studio creates both of these for you, so it’s really pretty easy to set up.

Listing 4 shows the LocationsEventReceiver service. You may notice it’s structured a little differently than the server side event receiver. The service must implement the IRemoteEventService interface, and handle both synchronous and asynchronous events (in this case, the synchronous event handler does nothing). Instead of having a different method for each kind of event, a single method is called with properties that include an EventType. You may also note that the “After” properties actually work! However, don’t be lulled into complacency here; I’ve found them to work differently in other situations.

public class LocationsEventReceiver : IRemoteEventService
{
    // Handle synchronous event (does nothing)
    public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
    {
        return new SPRemoteEventResult();
    }
 
    // Handle asynchronous event (tries to geocode the item)
    public void ProcessOneWayEvent(SPRemoteEventProperties properties)
    {
        if (properties.EventType == SPRemoteEventType.ItemAdded ||
            properties.EventType == SPRemoteEventType.ItemUpdated)
        {
            CheckGeoCode(properties);
        }
    }
 
    // CheckGeoCode - Checks to see if the item needs to be geocoded, and if so, does it
    private void CheckGeoCode(SPRemoteEventProperties properties)
    {
        Guid listId = properties.ItemEventProperties.ListId;
        int listItemId = properties.ItemEventProperties.ListItemId;
 
        // Extract the "After" properties
        Dictionary<string, object> afterProperties = 
                properties.ItemEventProperties.AfterProperties;

        object location = afterProperties.ContainsKey("GeoLocation") ?
                  Geo.SPGeoLocationField.GetFieldGeolocatioNValue(
                  new Geo.GeoLocation(afterProperties["GeoLocation"] as string)) : null;
        string geoCheck = afterProperties.ContainsKey("GeoCheck") ?
                  afterProperties["GeoCheck"] as string : null;
        string address = afterProperties.ContainsKey("WorkAddress") ?
                  afterProperties["WorkAddress"] as string : null;
        string city = afterProperties.ContainsKey("WorkCity") ?
                  afterProperties["WorkCity"] as string : null;
        string state = afterProperties.ContainsKey("WorkState") ?
                  afterProperties["WorkState"] as string : null;
        string country = afterProperties.ContainsKey("WorkCountry") ?
                  afterProperties["WorkCountry"] as string : null;
        using (ClientContext clientContext =
            TokenHelper.CreateRemoteEventReceiverClientContext(properties))
        {
            Geo.SPAccess.CheckListItemGeoCode(clientContext, listId, listItemId,
                                    address, city, state, country, location, geoCheck);
        }
    }
}

Listing 4 – Remote Event Receiver in SharePoint 2013

Displaying the Locations in SharePoint 2013

At this point, each solution has a Locations list containing locations to plot on the map, and an event receiver to geocode the address in each Locations list item. This is a good time to stop and see what SharePoint 2013 provides out of the box, which makes it worth using the Geolocation field type.

The picture below shows the locations list in SharePoint 2013 after the event receiver has run and geocoded the list items. The Geolocation field is shown as a small globe when it’s filled in, and hovering the mouse over the globe reveals a pop-up map – very cool!

There’s another bonus in here as well, however! If you look at Schema.xml under the Locations list (in the SharePoint 2013 App’s Locations project), you can see that I added something called a Map View to the list. This is built into SharePoint; you can enable it on any list that contains a Geolocation field. When you click on the Map View, you can see multiple list items highlighted on a map. Clicking a location on the map highlights the corresponding list item and vice-versa. This is such a great feature, it’s a wonder that the SharePoint team hid it so well!

Displaying the Locations in a Web Part

But wait – our mission wasn’t just to use a map view in SharePoint 2013! It was to make a web part that works in both 2010 and 2013. Once again, the code is the same, but the packaging is different in the 2010 solution and 2013 app.

In SharePoint 2010, the web part is implemented as a Visual Web Part. In the SharePoint 2013 App, it’s a page in the provider hosted app, running outside of SharePoint. Visual Web Parts are really ASP.NET “User Controls,” and are sort of like miniature web pages, which allows the code to be nearly identical between the two projects. (A bit of trivia: in pre-release versions of ASP.NET, User Controls were called “pagelets” which makes a lot more sense than “user controls” – after all, aren’t all controls for users to interact with? Perhaps “pagelet” sounded too much like a Java technology for Microsoft’s taste.)

Listing 5 shows the web part in the SharePoint 2010 solution; it’s in a folder called ShowMap\LocationsWebPart, and is called LocationsWebPart.ascx. As you can see, most of the page is involved with loading various Javascript files, which ultimately will run on the browser to render the map. There is also a <div> element which will hold the map.

<script src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1" type="text/javascript" charset="UTF-8"></script>
<script type="text/javascript">
     // Register jQuery and the web part script
    RegisterSod('jquery.min.js', '<%=siteUrl%>/Scripts/jquery-1.7.1.min.js');
    LoadSodByKey('jquery.min.js');
    RegisterSod('app.js', '<%=siteUrl%>/Scripts/App.js');
    LoadSodByKey('app.js');

    // Wait until all scripts are loaded, then run the web part script
    ExecuteOrDelayUntilScriptLoaded(function () {
        ExecuteOrDelayUntilScriptLoaded(function () {
            ExecuteOrDelayUntilScriptLoaded(function () {
                LoadMap();
            }, 'sp.js');
        }, 'app.js');
    }, 'jquery.min.js');
 </script>

<div>
    <asp:Label ID="MessageLabel" runat="server" Text=""></asp:Label>
    <asp:Literal ID="JSonData" runat="server"></asp:Literal>
    <div id="Map" style="height:400px; width:600px;"></div>
</div>

Listing 5 – Web Part to display the Locations Map

The SharePoint 2013 app version is nearly identical, and can be found in the Pages folder in a file called ShowMap.aspx. The main difference is that the page includes a <form> element, as required by ASP.NET. Both web parts include a small amount of code behind, which populates a Literal control with the data to be plotted. The code creates a SharePoint ClientContext object, and then calls PlotLocations() – already shown in Listing 3 – to place the list of locations on the page in JSON format.

An interesting difference between the SharePoint 2010 and 2013 versions of the web part is how they handle web part settings. Both web parts can be configured to show the user’s choice of map type (auto, road, aerial, or bird’s eye), and to show or hide the Bing Maps “dashboard”. The SharePoint 2010 web part simply declares two properties for these values, as shown in Listing 6. Their values are passed into PlotLocations() for rendering on the client.

    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    public bool ShowDashboard { get; set; }
 
    public enum MapStyles
    {
        Auto, Road, Aerial, Birdseye
    }
 
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    public MapStyles MapStyle { get; set; }

Listing 6 – Traditional Server-side Web Part Properties

This works a little differently in SharePoint 2013 Apps. App web parts (sometimes called “App Parts”) are really always the same web part under the covers, the client web part. The client web part generates an IFrame to show a page in the App Web (for a SharePoint hosted app) or an external site (as in this case.) Web part settings are specified in the XML declaration for the client web part, and then passed to the page as query string parameters. Listing 7 shows this declaration. You can find it in the Locations project under LocationsMap\Elements.xml.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ClientWebPart Name="LocationsMap" Title="Locations Map" 
   Description="Displays a map of locations in the Locations app" DefaultWidth="620"
   DefaultHeight="420">
 
    <!-- Content element identifies the location of the page that will render inside
         the client web part. Properties are referenced on the query string using the
         pattern _propertyName_
         Example: Src="~appWebUrl/Pages/ClientWebPart1.aspx?Property1=_property1_" -->
    <Content Type="html" Src="~remoteAppUrl/Pages/ShowMap.aspx?{StandardTokens}
&amp;showDashboard=_ShowDashboard_&amp;style=_MapStyle_" />
 
    <!-- Define properties in the Properties element.
         Remember to put Property Name on the Src attribute of the Content element above.
    -->
    <Properties>
      <Property Name="ShowDashboard"
                RequiresDesignerPermission="false"
                Type="boolean"
                DefaultValue="true"
                WebBrowsable="true"
                WebDisplayName="Show Dashboard"
                WebCategory="Map"
                WebDescription="Enables the display of map controls"/>
      <Property
                Name="MapStyle"
                Type="enum"
                RequiresDesignerPermission="false"
                DefaultValue="auto"
                WebCategory="Map"
                WebDisplayName="Map Style"
                WebDescription="Controls the type of map rendering">
        <EnumItems>
          <EnumItem WebDisplayName="Auto" Value="auto"/>
          <EnumItem WebDisplayName="Road" Value="road"/>
          <EnumItem WebDisplayName="Aerial" Value="aerial"/>
          <EnumItem WebDisplayName="Birds eye" Value="birdseye"/>
        </EnumItems>
      </Property>
    </Properties>
  </ClientWebPart>
</Elements>

Listing 7 – Web Part settings for the Client Web Part

Now that the scripts are loaded and the locations are on the page (or “pagelet”) in JSON format, all that remains is to render the points on the map using the Bing Maps API. The Bing Maps API is a lot of fun! It’s the only API I know of that has a visual SDK … it’s great to experiment with the visual SDK and, when you find what you want, to see how it’s done in Javascript. The Bing Maps Interactive SDK can be found here.

Listing 8 shows the code to display the map. The code is identical in both versions, and can be found in the Scripts folder in a file called App.js. Notice how easy it is to access the map information, including the Bing Maps key, map style, and all the points to be plotted. Because they were placed on the page in JSON format, they can are simple objects to the Javascript.

function LoadMap() {
 
    if (locations.Items.length > 0) {
 
        var loc = locations.Items[0];
 
        // Get general rendering information
        var credentials = locations.Credentials;
 
        var mapStyle;
        switch (locations.MapStyle)
        {
            case "auto":
                mapStyle = Microsoft.Maps.MapTypeId.auto;
                break;
            case "road":
                mapStyle = Microsoft.Maps.MapTypeId.road;
                break;
            case "aerial":
                mapStyle = Microsoft.Maps.MapTypeId.aerial;
                break;
            case "birdseye":
                mapStyle = Microsoft.Maps.MapTypeId.birdseye;
                break;
        }

        var showDashboard = (locations.ShowDashboard.toLowerCase() == 'true');       
        map = new Microsoft.Maps.Map(document.getElementById('Map'),
            {
                'credentials': credentials,
                'height': 400, 'width': 400,
                'mapTypeId': mapStyle,
                'showDashboard': showDashboard
            });
 
        // Variables to store the bounds of the map
        var topLeftLat = loc.Latitude;
        var topLeftLon = loc.Longitude;
        var bottomRightLat = loc.Latitude;
        var bottomRightLon = loc.Longitude;
 
        // Loop through the locations
        for (var i in locations.Items) {
            var lat = locations.Items[i].Latitude;
            var lon = locations.Items[i].Longitude;
            var name = locations.Items[i].Name;
 
            // Ensure the bounds of the map include this point
            if (lat < topLeftLat) topLeftLat = lat;
            if (lon < topLeftLon) topLeftLon = lon;
            if (lat > bottomRightLat) bottomRightLat = lat;
            if (lon > bottomRightLon) bottomRightLon = lon;
 
            // Add a pushpin
            var pushpin = new Microsoft.Maps.Pushpin(
                new Microsoft.Maps.Location(lat, lon), null);
            map.entities.push(pushpin);
 
            // Add the contact name
            var pushpin2Options = {
                width: null, height: null,
                htmlContent: "<div style='font-size:12px;font-weight:bold;border:solid 2px;background-color:White'>" + name + "</div>"
            };
            var pushpin2 = new Microsoft.Maps.Pushpin(
                new Microsoft.Maps.Location(lat, lon), pushpin2Options);
            map.entities.push(pushpin2);
        }
 
        // Increase the boundary coordinates to provide some space around the edge
        var spread = Math.max(bottomRightLat - topLeftLat, bottomRightLon - topLeftLon) * .15;
        topLeftLat -= spread;
        topLeftLon -= spread;
        bottomRightLat += spread;
        bottomRightLon += spread;
 
        // Now show the map within this boundary
        map.setView({
            bounds: Microsoft.Maps.LocationRect.fromLocations(
                new Microsoft.Maps.Location(topLeftLat, topLeftLon),
                new Microsoft.Maps.Location(bottomRightLat, bottomRightLon))
        });
    }
}

Listing 8 – Javascript to show the map in the LocationsMap web part

Conclusion

Microsoft’s new App model has certainly moved the cheese for SharePoint developers. But it has a number of advantages, such as better isolation and reliability. It treads lightly on the SharePoint server, so Apps work in shared environments such as Office 365 or restrictive corporate SharePoint installations.

As this and the previous article demonstrate, you don’t need to wait for the App model or even for SharePoint 2013 to get started. Moving your code off of SharePoint isn’t hard, it’s just different. And when it’s time to move to the App model, you’ll be ready, and only minor changes to the packaging will be needed. Not only will your code be easier to migrate, but your skills will be too.

I’d love to hear your comments and ideas; please let me know what you think. And thanks for reading!

Blog - Comment List MSDN TechNet
  • Loading...
Leave a Comment
  • Please add 4 and 5 and type the answer here:
  • Post