Introduction
I read recently that in just a couple of years over a third of all development will be cloud based. That's a big shift. So I started to look into some of the technologies and what it takes to develop applications for the cloud. Now to be clear, I think there might be some misconceptions regarding what a cloud application is. I'm not referring to web server based applications. The development that I'm talking about would be applications for platforms like Azure.
The only way that I know to learn is by doing; experience is definitely my best teacher. Unfortunately, the day job rarely provides the diversity in challenges or the opportunities in creativity to explore all the new technologies. So for me, an alternative is to always think like an entrepreneur. I look at the new technologies and think of new businesses or ventures that could be started using the new technologies. In examining this new paradigm, I find that there is an overwhelming number of potential opportunities. I say that because I don't think that the change is going to be to simply to move existing applications to a new environment. Instead, I think the technologies, tools, and infrastructure are all in place for a whole new wave of solutions that were previously not feasible.
We may argue, but I believe a key player in these new solutions will be the device we all carry around with us, the cell phone. Actually, I don't think we should call them cell phones any more. Because they are really 'wireless personal computers' that happen to also allow you to make phone calls. These devices have the power and capabilities that surpass those of a laptop of just a couple of years ago. Given the availability of broadband speed to support these devices, the sky is the limit as far as application capability goes. And I'm not just talking about cute pastime applets or games; I'm talking about real world business solutions.
So the primary goal here is to learn what it takes to develop applications for Azure, but not just simply the mechanics of packaging and deploying an application. Specifically, I want to explore using Silverlight on both desktop and phone (as a single solution) deployed on Azure. But this goal is still too broad for the purpose of conceptualizing specific applications. So to narrow things down, we'll bring in Bing Maps as the central theme in exploring solutions for the cloud. You'd be amazed at how many other usages can be devised besides the typical usage for traveling directions.
Since Bing Maps will form the core of the exploration, we'll start by getting acquainted with that service and the associated Silverlight control in this article. In the next article, I'll describe some cloud application concepts centered around Bing Maps. At the same time, we'll expand on the map functionality presented in this article to highlight some functionality of the concepts. And in the third article, we'll cover a complete solution for the cloud, again utilizing Bing Maps. One final introduction note, since this is a learning exercise for me, the prose is more on the line of a tutorial in a step by step fashion. The student is really me, but you are invited to follow along.
Getting Started With Bing Maps
In order for you to get access to the map services provided by Microsoft, you need to sign up and get an account. You can sign up for a developer account at http://www.microsoft.com/maps/developers/web.aspx. Once you sign up, you'll be able to get a Bing Maps key which you can then use to get free access to the service. All the information you will need is provided on the above link.
We are also going to make extensive use of the Silverlight Map control, so you will need to download that also. You can find the SDK and associated instructions here: http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&FamilyID=beb29d27-6f0c-494f-b028-1e0e3187e830.
Now that we're done with the external pre-requisites, let's see how we can apply them. Start by creating a basic Silverlight application, name it SLMapTest, and accept the default ASP web hosting. To start with, since we want to use the Map control, you'll need to add a reference for the DLLs to the project (see below). You should find them in the Program Files folder under Bing Maps Silverlight Control-Libraries.
Now, add the map control to your page, along with the appropriate namespace, as shown below. Note that you will have to insert your own Bing Maps Credentials key (in place of 'Your Key') in order to get access to the service.
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
...
<Grid x:Name="LayoutRoot" Background="White">
<m:Map x:Name="bingMap" CredentialsProvider="Your Key">
</m:Map>
</Grid>
...
Compile and run. You have just provided your user with a fully interactive map. The user can scroll through the world map, change view modes, and zoom in to any desired area. Not bad for essentially no work on our part. Although that's not very impressive from the user's perspective (they can do that with Bing), it does prove that we have all the necessary pieces hooked up correctly.
Now let's add some basic functionality that will allow the user to enter a desired location/address and then have the system display a close up view of the area if it was found. Modify the page as shown below:
<m:Map x:Name="bingMap" CredentialsProvider="Your Key">
<TextBox Height="23" HorizontalAlignment="Right" Margin="0,26,50,0"
Name="textAddress" VerticalAlignment="Top" Width="120">
</TextBox>
<Button Content="Find" Height="23" HorizontalAlignment="Right" Margin="0,26,0,0"
Name="buttonFind" VerticalAlignment="Top" Width="44" Click="buttonFind_Click">
</Button>
<TextBlock Height="23" HorizontalAlignment="Right"
Name="textResult" VerticalAlignment="Top" Width="166"/>
</m:Map>
All we've done is to add a textbox for user entry, a button to invoke the request, and a TextBlock
to display any abnormal results. Here is what it will look like after we've hooked up things and the user has entered a location:
You have programmatic control of the map control through the methods and properties that it exposes (the documentation provided with the SDK has full description). The method we are interested in is SetView
, which centers the map on a set of coordinates that corresponds to the address of the location that the user entered. Unfortunately, the map control does not have a method which will accept a street address, only geo coordinates (latitude/longitude). That means that we have to perform the translation between a location address (as a string) and geo coordinates. Fortunately for us, Microsoft makes available a service which provides that functionality.
To continue with the project, we'll need to set up a proxy to the Bing GeoCode service which will provide the translation we need. Right click on the Solution Explorer reference node and select "Add Service Reference...". Type in the following for the address: http://staging.dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc?wsdl. Click GO to retrieve the file. Once the file is found and loaded, you should see the contract definition for the service in the left pane. Before you hit OK to have the wizard generate the proxy, make two additional changes. First, change the default namespace to something more descriptive like "BingGeocodeService" or whatever you'd like. The proxy class that gets generated by default brings in some type definitions that will clash with types already defined in the map control. To eliminate this, click on the Advanced button and then select "Reuse types in specified reference assemblies". Proceed by selecting the two map assemblies. This will eliminate those definitions from the generated proxy namespace. Finally click OK to generate the proxy.
In order for us to make use of the service, add a member variable for the proxy in the code-behind file, as shown below (don't forget the namespace):
#region private property
private BingGeocodeService.GeocodeServiceClient geocodeClient;
private BingGeocodeService.GeocodeServiceClient GeocodeClient
{
get
{
if (null == geocodeClient)
{
BasicHttpBinding binding =
new BasicHttpBinding(BasicHttpSecurityMode.None);
UriBuilder serviceUri = new UriBuilder(
"http://dev.virtualearth.net/webservices/" +
"v1/GeocodeService/GeocodeService.svc");
geocodeClient = new BingGeocodeService.GeocodeServiceClient(binding,
new EndpointAddress(serviceUri.Uri));
geocodeClient.GeocodeCompleted += new
EventHandler<binggeocodeservice.geocodecompletedeventargs>(
GeocodeCompleted);
}
return geocodeClient;
}
}
We are ready to start hooking things together. Create an event handler for the button Click
event. This will occur when the user depresses the Find button. In that handler, we'll add some code that will use our newly created proxy to make a call to the Bing Geocode service. The contract indicates a method, appropriately named GeocodeAsync
, which can take an address string (in the form of a query) and return, through a callback, the latitude/longitude values for the location. We will then in turn, pass those values to the map control to center the view around the coordinates.
Since the call to the service is an asynchronous call, we created an event handler (GeocodeCompleted
) for the reply when the endpoint was being instantiated (see above). In the button click event handler, we grab the address string the user entered and pass it to a helper method, GeocodeAddress
, where the call to the service is made. The following code shows the three methods:
private void buttonFind_Click(object sender, RoutedEventArgs e)
{
if (textAddress.Text.Length > 0)
{
GeocodeAddress(textAddress.Text);
}
}
...
private void GeocodeAddress(string address)
{
BingGeocodeService.GeocodeRequest request =
new BingGeocodeService.GeocodeRequest();
request.Query = address;
request.ExecutionOptions = new BingGeocodeService.ExecutionOptions();
request.ExecutionOptions.SuppressFaults = true;
request.Options = new BingGeocodeService.GeocodeOptions();
request.Options.Filters =
new ObservableCollection<BingGeocodeService.FilterBase>();
BingGeocodeService.ConfidenceFilter filter =
new BingGeocodeService.ConfidenceFilter();
filter.MinimumConfidence = BingGeocodeService.Confidence.High;
request.Options.Filters.Add(filter);
request.Credentials = new Credentials();
request.Credentials.ApplicationId =
(string)App.Current.Resources["BingCredentialsKey"];
GeocodeClient.GeocodeAsync(request);
}
...
private void GeocodeCompleted(object sender,
BingGeocodeService.GeocodeCompletedEventArgs e)
{
string callResult = "";
try
{
if (e.Result.ResponseSummary.StatusCode !=
BingGeocodeService.ResponseStatusCode.Success ||
e.Result.Results.Count == 0)
{
callResult = "Could not find address.";
}
else
{
bingMap.SetView(e.Result.Results[0].Locations[0], 18);
}
}
catch
{
callResult = "Error processing request.";
}
textResult.Text = callResult;
}
Try it. Compile and run the application to make sure you can access the geocode service. Yeah I know, you can just go to Bing Maps and get a lot more information returned. But the plan is not to provide that type of service, but instead make use of what's readily available in new ways. At this point, we are still just checking the plumbing. But before we start on the road of exploring new functionality that we can create using the map control, I think I'd like to take a little detour into the land of MVVMness. Primarily because once we start adding more code, things will get pretty messy if we continue with our current approach. And secondarily because...
MVVMness
There is a lot to be said about MVVM. And in fact, a simple search will overwhelm you with all that is being said. How can there be so much diversity in opinions, and approaches to a pattern? A pattern by definition is supposed to do the opposite, that is, provide a simple, easy to understand approach to solving a problem. A pattern is a communication mechanism. In my opinion, we're missing the forest for the trees.
If we step back and look at it from another perspective, perhaps it may become clearer (or not). I think, as a community, we've come to a realization of what we want (and need) and have labeled it with the 'umbrella' term MVVM. MVVM is really a collection of techniques (some available through the framework, others improvised) that provide us the capability to satisfy the goals of good design: separated presentation; separation of concerns; testability; etc. That's really too much to be defined as a pattern.
Martin Fowler writing about MVC here states: "It's often referred to as a pattern, but I don't find it terribly useful to think of it as a pattern because it contains quite a few different ideas." MVVM, being a variation of MVC, would fall under the same critique. And in this blog on MVVM, John Grosman writes: "My team has been using the pattern feverishly for a couple of years, and we still don't have a clean definition of the term ViewModel" (note, emphasis mine). "One problem is (that) ViewModel actually combines several separate, orthogonal concepts: view state, value conversion, commands for performing operations on the model."
I think each of the 'orthogonal concepts' mentioned above could by itself be described by its own pattern. And not only is it too much stuff to describe as a simple pattern, the mechanisms to implement those 'orthogonal concepts' have not been there (they have been dribbling in over time). In addition, the support that has been added has varied between the platforms! That should be enough to explain why there is so much confusion surrounding MVVM, but there is more in my opinion.
If an application consisted simply of a single View and its associated ViewModel/Model, then things could be OK (after you figure out how to implement the 'orthogonal concepts'). However, rarely are things that simple. In even the simplest applications, there will be several Views. In fact, simply displaying a dialog constitutes a separate View. Typically, you'll have several Views which will undoubtedly have some state/data linkage to each other. Buying into the separation of concerns requires that there be some kind of messaging infrastructure available to the ViewModels. This is one additional hole that exists in the support required to implement MVVMness. Fortunately, this gap can be filled through the use of various community toolkits and libraries. The dilemma with this approach is that the lifetime of your application may be longer than the support from the community. And Microsoft may have other plans which may not be completely in line with the library you selected. So hopefully, a messaging mechanism will be incorporated into future releases of the .NET Framework (and a dependency injection facility would be nice too).
Breaking Up Is Hard To Do
Yeah, I know. Most readers won't relate. Anyway, within the context of our sample application, the break up is going to be facilitated by using the MVVM Light Toolkit, which you can find here. The toolkit provides several 'facilities' that assist in pursuing MVVMness, including: a mechanism to tie the View and the ViewModel together; an implementation for ICommand
; and a nice messaging mechanism. And it also integrates nicely with Visual Studio and Blend. There are other toolkits and libraries available, I just happened to use this one for this application.
Once you've installed the MVVM Light Toolkit, the first thing that's needed to continue with our project is to add references to the GalaSoft MvvmLight library DLLs. There are separate libraries for the different platforms, so pull in the ones for Silverlight 4 for this project. Then create a folder in the project, call it ViewModel, and add a MvvmViewModel(SL) as MainViewModel, and a MvvmViewModelLocator(SL) as ViewModelLocator, to the new folder. These templates were added as part of the MvvmLight installation.
The MvvmLight template classes provide commented instructions within them that indicate what's needed. But we'll go through the steps here. First, add the ViewModelLocator to the application resources (in App.xml) as shown below:
<Application
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">
http://schemas.microsoft.com/winfx/2006/xaml</a>"
xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008">
http://schemas.microsoft.com/expression/blend/2008</a>"
xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006">
http://schemas.openxmlformats.org/markup-compatibility/2006</a>"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="SLMapTest.App"
xmlns:vm="clr-namespace:SLMapTest.ViewModel"
mc:Ignorable="d">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
</Application>
Separated presentation in MVVMness means that the View and its associated ViewModel can be designed independently. However, at some point, they need to be introduced to each other. One mechanism to accomplish this is provided by the aptly named ViewModelLocator. It is essentially a global lookup for a View:ViewModel association. So we'll continue by adding an entry in the ViewModelLocator for the new ViewModel class we created above, MainViewModel
. You can do this with a code snippet provided with the MvvmLight installation and described in the ViewModelLocator template comments. The end result is that the MainViewModel
is added as a bindable property to the class.
Now we can bind the MainPage view to the associated MainViewModel
class by modifying MainPage.xml as follows:
...
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelMain}">
...
If you compile and run the application, you may notice that nothing really has changed. But if you place a breakpoint in the MainViewModel
class constructor, you will see that it is indeed being created, so now we just need to make use of it.
So the whole idea behind MVVMness is to have as little as possible (or nothing) in the code-behind file so that the View can be designed independently from the business logic (starting with the ViewModel), and that the business logic can also be tested independently from the View, thus requiring no user interaction. However, I think that the former should be a goal, not a mandate. If you have to complicate the application to an extreme, simply to eliminate something from the code-behind, stop and think about it. Is the result worth the effort or complexity created?
Continuing with our project, in order for the MainViewModel
to support MainPage (View), we need to add some binding properties. Specifically, we need support for the button and two text boxes. The code below shows the class ready for binding with the View:
public class MainViewModel : ViewModelBase
{
RelayCommand findButton;
string findString;
string resultString;
public MainViewModel()
{
findButton = new RelayCommand(FindButtonClick);
}
#region Commanding
public ICommand FindButtonCommand
{
get { return findButton; }
}
void FindButtonClick()
{
}
#endregion
#region data context
public string FindString
{
get { return findString; }
set { findString = value; }
}
public string ResultString
{
get { return resultString; }
set { resultString = value; }
}
#endregion
}
If you bring up the properties window for the controls, you'll be able to do all of the bindings interactively; it beats typing. But make sure you compile first, otherwise the bindable properties won't show up. Here is how the binding was defined:
...
<TextBox Height="23" HorizontalAlignment="Right" Margin="0,26,50,0"
Name="textAddress" VerticalAlignment="Top" Width="120"
Text="{Binding Path=FindString, Mode=TwoWay}">
</TextBox>
<Button Content="Find" Height="23" HorizontalAlignment="Right"
Margin="0,26,0,0" Name="buttonFind" VerticalAlignment="Top"
Width="44" Command="{Binding Path=FindButtonCommand}">
</Button>
<TextBlock Height="23" HorizontalAlignment="Right" Name="textResult"
VerticalAlignment="Top" Width="166" Text="{Binding Path=ResultString}"
Foreground="#FFEA1818" />
...
Now we can move the remaining code that we had in the code-behind file for MainPage to its ViewModel. You can peruse through the complete code in the downloadable project.
There's only one hiccup at this point (there will be many others) in moving the code out of the code-behind file. The MainViewModel
class does not know about the map control. And it shouldn't. The map control is controlled through method calls, so there is no easy binding mechanism that allows access to all the functionality we'll need. I did some searching to see if there was a quick answer to the problem, but did not come up with anything. I also know that I intend to add some more interaction than is typical for a map viewer, so I don't want to paint myself into a corner from the beginning. If I later find a way to move the functionality to the ViewModel and make it cleaner, I'll do it then. For now, we'll leave some of that interaction in the code-behind file for MainPage.
So how can the MainViewModel
handle the button click message but have the View react to the event without the MainViewModel
having a linkage to the View? The answer is to use messages. Included with the MVVM Light Toolkit is a nice generic messaging infrastructure. It's essentially a publication service that allows subscribers to 'register' to receive a message and publishers to 'send' (publish) a message. It is very similar to the EventAggregator available in Prism.
In our situation, we can have the MainPage (View) register to be notified when the MainViewModel
processes the button click event. The following code shows the change in both classes. First, a definition of a SetMapViewMsg
message:
namespace SLMapTest.Messages
{
public struct SetMapViewMsg
{
public Location CenterLocation;
}
}
The message contains the latitude/longitude values for the translated address the user entered, as a Location
property. The MainPage (View) subscribes to the message since it knows what to do with the values, that is, pass them to the map control. And the MainViewModel
publishes the messages since it has access to the service which does the translation. Here's how the subscription is done:
public MainPage()
{
InitializeComponent();
Messenger.Default.Register<SetMapViewMsg>(this, SetViewHandler);
}
#region message handler
void SetViewHandler(SetMapViewMsg msgData)
{
bingMap.SetView(msgData.CenterLocation, 18);
}
#endregion
And here is the revised GeocodeCompleted
in MainViewModel to implement the publication:
...
else
{
Location geoLocation = new Location(
e.Result.Results[0].Locations[0].Latitude,
e.Result.Results[0].Locations[0].Longitude);
Messenger.Default.Send<SetMapViewMsg>(
new SetMapViewMsg() { CenterLocation = geoLocation });
}
...
Now if we compile and run the application, we should be back to where we were, but with a better foundation to add the additional functionality to come. But before we start on that, there is just one annoying (to me) part of the UI that needs to be addressed.
It's The Little Things
When the user enters an address in the textbox, the most natural action is to terminate it with the Enter key. But as you can see, if you run the app, nothing happens. The user needs to click on the button in order to process the entry. That's annoying, so I think we need to address that. The result we want is that when the keyboard Enter key is depressed (for the textbox), the system behaves the same way as if the Find button was depressed. It's just more intuitive.
To do this, we need to inspect each character that is being entered so we know when the Enter key comes through. Normally, you can do this by implementing a handler for the KeyUp event. But since we are pursuing MVVMness, we can achieve that functionality using behaviors, and specifically in this solution, we can make use of the EventToCommand behavior provided with the MVVM Light Toolkit. The cool feature here is that it allows us to pass the event arguments which we need. Here is the change for the MainPage XAML. It essentially generates a method call to KeyUpCommand
, passing to the method the event parameters, whenever the KeyUp event is generated on the textbox.
...
<TextBox Height="23" HorizontalAlignment="Right"
Margin="0,26,50,0" Name="textAddress" VerticalAlignment="Top"
Width="120" KeyUp="textAddress_KeyUp" Text="{Binding Path=FindString, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<cmd:EventToCommand
Command="{Binding Path=KeyUpCommand, Mode=OneWay}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
...
As you can see, we are setting a trigger on the KeyUp event which will call the method (delegate) defined by the KeyUpCommand, and we are passing the Event Args to the method. So in the MainViewModel
, we have to create the RelayCommand
(which implements ICommand
) property and define the method to be called for each KeyUp event. Here's the code in summary form (you can see the completed code in the downloadable project):
...
RelayCommand<KeyEventArgs> keyUpCommand;
public MainViewModel()
{
findButton = new RelayCommand(FindButtonClick);
keyUpCommand =
new RelayCommand<KeyEventArgs>(TextFieldCharEntry);
}
...
public ICommand KeyUpCommand
{
get { return keyUpCommand; }
}
void TextFieldCharEntry(KeyEventArgs e)
{
if (e.Key.Equals(Key.Enter))
{
if (findAddress != null && findAddress.Length > 0)
{
GeocodeAddress(findAddress);
}
}
}
...
Pretty straightforward on this side, right? So go ahead and compile and run the application. Enter an address and press the Enter key in the textbox to verify you don't need to use the Find button.
Almost...But Not Exactly
If you were following along, you would have noticed that nothing happened! So let's see if we're at least getting the keystroke events. Place a breakpoint inside the block that checks for the Enter key, shown in the code block above. Run it again. You'll note that the characters are indeed being passed and the Enter key is being detected. But if you examine the findAddress
property, you'll notice that it is null! And that's why the code is not being executed. The problem is that the textbox does not perform the binding update until it loses (input) focus. Which makes sense, but causes us a problem as you can see. That is also why it works when the button is pressed (focus changed to the button).
Since we are getting each character as it is being entered, we could just update the local variable (for the address) at the same time we are checking for the Enter key. But there is another way that may be more appropriate and general, or just maybe more elegant. And this also demonstrates that there can be code in the code-behind file which is completely appropriate and does not detract from MVVMness. I don't think it takes anything away from the testability of the ViewModel.
The solution is to add a KeyUp event handler to the MainPage in the code-behind file. Then, in the handler, we can force the binding update when the Enter key is detected. Here's the code for the handler:
...
private void textAddress_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
var binding = textAddress.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
base.OnKeyUp(e);
}
...
That'll do it. Now compile and run the application. When the user presses the Enter key, the system responds in the same manner as pressing the Find button.
The Goal
Now we are ready to start adding some new map related functionality to the application. We've got a good foundation to build on. We'll be looking at the map control with the goal of seeing how it could be used to improve typical business processes, as well as how it might be used to create new and novel solutions. However, even though Bing Maps is the visual glue, the final goal though is to tie the triumvirate of Silverlight, Azure, and Phone into a complete solution; that's my ultimate learning goal.
So in part 2, we'll continue by learning how to draw on the map, we'll do reverse geocode, routing, and other cool stuff. To be able to implement some of that, we'll make use of some other Bing Maps services. That's as far as I can see from this vantage point. I'm sure there are going to be some surprises and challenges that I can't foresee, and that's OK because that's going to add to the fun of learning.