After thinking about making clients to access the game and the fact that it is probably easier to create a generator tool on a desktop client (Thinking Windows 8 here); I thought it would probably be best to create an Web API web role now rather than later. The website after all is basically a Client as well.
So new way of thinking. All clients including the website don’t know anything about Entity Framework or Azure Service bus. All they need to know about is how to access the API. Having this as an extra Tier will also allow easier scaling in future.
Looks like I’ll be hitting the Pluralsight ASP.NET Web API course this weekend.
2012/11/28
Adding in an Extra Tier
2012/11/22
I feel a project coming together
As a way of trying to put all the stuff that I am learning together. I’ve come up with a project. I have been attempting to write a game for ages and have decided to make it using all the new Microsoft technologies. (You may have seen some of the preliminary work resulting in blog posts here.)
So this project will be a web based RPG game (With mobile clients to come later hopefully). It will be hosted in Azure and it will make use of Web Roles, Worker roles, SQL Azure, Azure Service Bus/ACS and Azure Storage. It will be written in Visual Basic .Net.
This is the website: https://www.tyranntrpg.org/
The front page has more details on what is going on and I hope to update the progress regularly here on this blog.
The current site doesn’t do too much as most of the work is going on behind the scenes. But the Codex part will get updates as things progress. In the images section of the codex you can see some of the artwork done by http://www.battleaxegfx.com/
This is one of the images (It will eventually be the Something has gone wrong image)

This project has been going on for a few years using different tools to create hence the collection of artwork and ideas.
Hopefully this time I’ll get it finished 🙂
2012/11/19
Hit a 404 error with a dot in an MVC 4 URL
Another quick post regarding passing a parameter that contains a “.” in it.
For example the below URL
http://myhost/home/edituser/me@mail.com
Started to cause a 404 error. I wasn’t sure why this was happening and after a lot of web surfing I came across the following information on Stack Overflow
Adding the following line into the HTTP <handlers> section for the path that contains the error:
<system.webServer>
....
<handlers>
....
<add name="ApiURIs-ISAPI-Integrated-4.0" path="/home/edituser/*" verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
Will fix the issue.
Displaying Formatted XML in an ASP.net Razor Web Page
A quick post for those looking to display a formatted XML document on an ASP.net Razor web page. The following code is in the controller and used to create the string to get passed through to the view. This example is based on displaying the information pulled from the Azure Management API as mentioned in previous blogs. The data is stored in a SQL Server database.
Function ShowRawXML(serviceOperationId As String, subscriptionName As String) As ActionResult
Dim model As New ShowRawXMLViewModel
Dim op = (From o In _db.ServiceOperations
Where o.ServiceOperationId = serviceOperationId And o.Subscription.FriendlyName = subscriptionName
Select o).Single
model.RawXML = vbCr & Server.HtmlEncode(op.RawXML)
model.SubscriptionName = subscriptionName
model.SubscriptionID = op.Subscription.SubscriptionID
model.ServiceOperationID = serviceOperationId
model.ServiceOperationType = op.Type.Name
Return View(model)
End Function
In the view, we are going to use the @Html.Raw function so we used the Server.HtmlEncode function to make it safe to display in HTML. Next we get to the razor view…
@ModelType AzureManager.Domain.ShowRawXMLViewModel
@Code
ViewData("Title") = "ShowRawXML"
End Code
<h2>Subscription ID [@Model.SubscriptionID] @Model.SubscriptionName</h2>
<h3>Show Raw XML for Service Operation @Model.ServiceOperationID</h3>
<h3>Operation Type: @Model.ServiceOperationType</h3>
<pre>
@Html.Raw(Model.RawXML)
</pre>
<p>
@Html.ActionLink("Back to List", "ServiceOperations", New With {.Id = Model.SubscriptionName})
</p>
Here you notice we display the raw XML inside a <pre></pre> tag. This will preserve the white space in the string. More information on this here: HTML <pre> Tag
2012/11/13
A VB example to access the azure management REST API
Hi,
This is based on an article in the Code Quick Start from MSDN, Original article is here: Code Quick Start: Create a console application that lists your Windows Azure hosted services
Below is the code which has been converted to VB.net. Everything else in the article is the same apart from creating a VB console program.
Imports System.Net
Imports System.Security.Cryptography.X509Certificates
Imports System.IO
Module Module1
Sub Main()
Try
' X.509 certificate variables
Dim certStore As X509Store
Dim certCollection As X509Certificate2Collection
Dim certificate As X509Certificate2
' Request and response variables
Dim httpRequest As HttpWebRequest
Dim httpResponse As HttpWebResponse
' Stream variables
Dim responseStream As Stream
Dim reader As StreamReader
' URI variable
Dim requestURI As Uri
' Specify operation to use for the service management call.
' This sample will use the operation for listing the hosted services.
Dim operation As String = "hostedservices"
' The ID for the Windows Azure subscription.
Dim subscriptionId As String = "{Your Subscription ID}"
' The thumbprint for the certificate. This certificate would have been
' previously added as a management certificate within the Windows
' Azure management portal.
Dim thumbPrint As String = "{Your Certificate Thumbprint}"
' Open the certificate store for the current user.
certStore = New X509Store(StoreName.My, StoreLocation.CurrentUser)
certStore.Open(OpenFlags.ReadOnly)
' Find the certificate with the specified thumbprint
certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbPrint,
False)
' close the certificate store
certStore.Close()
' Check to see if mat
If certCollection.Count = 0 Then
Throw New Exception("No certificate found containing thumbprint " _
& thumbPrint)
End If
' A matching certificate was found.
certificate = certCollection(0)
Console.WriteLine("Using certificate with thumbprint: " & thumbPrint)
' create new request
requestURI = New Uri(
String.Format("https://management.core.windows.net/{0}/services/{1}",
subscriptionId, operation))
httpRequest = HttpWebRequest.Create(requestURI)
' add certificate to requrest
httpRequest.ClientCertificates.Add(certificate)
' Specify the version information in the header
httpRequest.Headers.Add("x-ms-version", "2011-10-01")
' Make the call using the web request
httpResponse = httpRequest.GetResponse
' Display the response status code
Console.WriteLine("Response status code: " _
& httpResponse.StatusCode)
' Display thr request ID returned by windows azure
If httpResponse.Headers IsNot Nothing Then
Console.WriteLine("x-ms-request-id: " _
& httpResponse.Headers("x-ms-request-id"))
End If
' Parse the web response
responseStream = httpResponse.GetResponseStream
reader = New StreamReader(responseStream)
' Displa the raw response
Console.WriteLine("Response output:")
Console.WriteLine(reader.ReadToEnd)
' close the resources no longer needed
httpResponse.Close()
responseStream.Close()
reader.Close()
Console.ReadKey()
Catch ex As Exception
Console.WriteLine("Error encountered: " & ex.Message)
Console.ReadKey()
System.Environment.Exit(1)
Finally
System.Environment.Exit(0)
End Try
End Sub
End Module
2012/11/08
OData AddTo using WinRT and VB.net
I was trying to find a way to POST a new entity to an OData feed and found it hard to find examples of doing this. After a bit of searching I found some C# code that allowed me to do an update. After converting this to VB I ended up with
Private Sub addPersonButton_Click_1(sender As Object, e As RoutedEventArgs)
Dim p As New Person
p.name = personNameTextBox.Text
If p.name <> "" Then
_context.AddToPerson(p)
_context.BeginSaveChanges(AddressOf ContextSaveChanges, p)
End If
End Sub
Person is part of an OData feed I am using e.g. http://server/odata.svc/Person
So I create a new instance of a Person and add a name to it (From a TextBox in the UI)
Then I use the AddToPerson method of the context and as there is no synchronous way in WinRT to SaveChanges, I used the BeginSaveChanges method of the context and passed through the address of the routine that will handle the callback.
Private Function ContextSaveChanges(result As IAsyncResult) As Task
Try
_context.EndSaveChanges(result)
Catch ex As Exception
_message = ex.Message
If ex.InnerException IsNot Nothing Then
_message = _message & " - " & ex.InnerException.Message
End If
Me.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, AddressOf UpdateUI)
End Try
End Function
Public Sub UpdateUI()
personNameTextBox.Text = _message
End
Above is the function that gets called and it will try to run EndSaveChanges and trap any error that occurred and display it on the UI. I originally put the personNameTextBox.Text = ex.Message in the catch section but that caused a Treading error [RPC_E_WRONG_THREAD]. So after a lot of routing around and the help of a colleague I discovered the Me.Dispacher.RunAsync function in which I had a lambda expression in to update the UI. This didn’t fail but also didn’t update the UI so I used a subroutine and put the AddressOf in the RunAsync command and this worked as expected.
Phew made it 😉
2012/11/07
Windows Azure, Service Bus Queue between Webrole and Worker role
In a previous post I showed Azure Storage Queues being used to send messages between a Webrole and a Worker role.
I’ve got back onto this project to learn more about MCV 4 and Windows Azure Service Bus Queues. So I modified the original classes to use the SB Queues instead of Azure Storage queues. Behind the scenes Entity Framework Code First is being used to access the database. I’m loving the ability to change the classes and then run update-database to modify the SQL Azure database. Back to the queues.
First you need to create a queue which this article can walk you you through.
Once the queue is created put the connection string into the Azure Service Configuration file.
Below is the base class for a Job for this application.
Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Linq
Imports System.Net
Imports System.Threading
Imports Microsoft.ServiceBus
Imports Microsoft.ServiceBus.Messaging
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.Diagnostics
Imports Microsoft.WindowsAzure.ServiceRuntime
Imports Microsoft.WindowsAzure.Storage
Public MustInherit Class tjob
' Enable database access
Protected Friend _db As New TyranntDb()
' A place holder for the user information
Protected Friend _usr As Member
' setup variables to allow for access to Azure Queues
Protected Friend _jobQueue As QueueClient
' A sample of a job entry in the Queue
Private sample = <job type="email" userid="102">
<type/>
</job>
' the user ID which is used to pull the user information
Private _userID As Int64
' Initialise the job from the XML String
Public Sub New(jobStr As String)
Dim jobXML As XElement = XElement.Parse(jobStr)
_JobType = jobXML.@type
Dim usrstr As String = jobXML.@userid
Try
UserID = Convert.ToInt64(usrstr)
Catch ex As Exception
ErrorMessage = ex.Message
ReportError("tJob New Convert int64")
End Try
End Sub
' Create a blank job, this is used for creating a job to
' put onto the queue.
Public Sub New()
_JobType = ""
_userID = -1
End Sub
' Job type. Used to create the correct object.
Public Property JobType As String
' The user ID. If this is being set then it
' will look up the user from the database
Public Property UserID As Integer
Get
Return _userID
End Get
Set(value As Integer)
_userID = value
If _userID > 0 Then
GetUserDetails()
End If
End Set
End Property
' This is the code that "Processes" the job. Each job type must
' implement this code.
Public MustOverride Function Process() As Boolean
' A general variable for storing any errors that
' occur. If it's empty then no errors are assumed.
Public Property ErrorMessage As String
' This will generate an XML element that describes the job.
Public MustOverride Function ToXML() As XElement
' This will generate a string version of the XML
' which describes this job.
Public Overrides Function ToString() As String
Return ToXML.ToString
End Function
' This routine will pull the user information from the
' database and store the user detals in the _usr object.
Protected Friend Sub GetUserDetails()
Dim q = From u In _db.Members
Where u.ID = _userID
Select u
If q.Count > 0 Then
_usr = q.Single
End If
End Sub
' If the job is being created. This function will add the job
' to the Azure Queue.
Public Sub AddJobToQueue()
' Get the azure storage account object.
_jobQueue = QueueClient.CreateFromConnectionString(CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString"), "standard_queue")
Try
' Now add the job details to the queue.
Dim msg As New BrokeredMessage(Me.ToString)
_jobQueue.Send(msg)
Catch ex As Exception
_ErrorMessage = ex.Message
ReportError("AddJobToQueue")
End Try
End Sub
Public Sub ReportError(location As String)
Dim err As New ErrorMessage
err.ErrorTime = DateTime.Now
err.Location = location
err.Message = Me.ErrorMessage
_db.ErrorMessages.Add(err)
_db.SaveChanges()
End Sub
End Class
As you can see, this class must be inherited by another class which will perform the actual job. The base class will handle getting a users information from the database represented by the TyranntDB context class. I’ve added an error reporting system which will store any errors into a table in the database. The job information is stored in an XML format (The message queues allow for 64kb message which should be plenty for our purposes), so the inheriting class must implement a ToXml method. Also the inheriting job class must know how to do the job it is intended to perform so it must implement a Process method as well. Finally the class must be able to add its self to the message queue. So this base class has an AddJobToQueue method which makes use of the ToXml method the inheriting class has implemented to generate the message body to be added onto the queue. The service bus Imports at the top of the class will expose the QueueClient and the BrokeredMessage which is all that is required to add a message to the queue. You can see in the code how simple it is.
Next we need to add a new class that is derived from this base class to add a new member to the database. Below is the code that achieves this.
Public Class tjNewUser
Inherits tjob
' an example of a new user job
Private sample = <job type="newuser" userid="-1">
<user name="{username}" email="{user)@{domain}">Full Name</user>
</job>
' Extra data required by this class
Public Property userName As String
Public Property email As String
Public Property fullName As String
Public Sub New(jobStr As String)
' initialise basic information
MyBase.New(jobStr)
Dim jobXML As XElement = XElement.Parse(jobStr)
' now initialise new user information
userName = jobXML...<user>.@name
email = jobXML...<user>.@email
fullName = jobXML...<user>.Value
End Sub
Public Sub New()
' initialise the base information
MyBase.New()
JobType = "newuser"
userName = ""
email = ""
fullName = ""
End Sub
' Create the new user in the database
Public Overrides Function Process() As Boolean
' first check to see if the user already exists
Try
Dim r = From m In _db.Members
Where m.MemberAlias = _userName
Select m
If r.Count > 0 Then
' User already exists so do not continue
' return true in this case as request
' has been processed more than one.
ErrorMessage = "User " & _userName & " Already exists"
ReportError("tjNewUser Process")
Return True
End If
Catch ex As Exception
ErrorMessage = ex.Message
ReportError("tjNewUser lynq query to get member ID")
End Try
' create a new user
Dim usr As New Member
' populate the generic information
usr.EmailAddress = _email
usr.Name = _fullName
usr.MemberAlias = _userName
usr.LastLogin = DateTime.Now
' now set the user group to be member
Dim userType As MemberType
Try
userType = (From m In _db.MemberTypes
Where m.Name = "Member"
Select m).Single
Catch ex As Exception
ErrorMessage = ex.Message
ReportError("tjNewUser Lynq query to get 'Member' member type")
Return False
End Try
usr.Type = userType
' now save the user
Try
_db.Members.Add(usr)
_db.SaveChanges()
Catch ex As Exception
ErrorMessage = ex.Message
ReportError("tjNewUser Memebers.Add Save Changes")
Return False
End Try
' Now that the user was sucessfully created,
' generate a new user email job
Dim jb As New tjEmail
jb.EmailType = "NewAccount"
jb.UserID = usr.ID
' Add the job to the Azure job queue
jb.AddJobToQueue()
If jb.ErrorMessage = "" Then
Return True
Else
ErrorMessage = jb.ErrorMessage
ReportError("tjNewUser Add Job to Queue produced error")
Return False
End If
End Function
Public Overrides Function ToXML() As XElement
Return <job type="newuser" userid=<%= UserID %>>
<user name=<%= _userName %> email=<%= _email %>><%= _fullName %></user>
</job>
End Function
End Class
As you can see, this class has overrode the ToXML function and the Process Function which has the code that actually adds the user to the Members database. In the last part of the process function it creates a new job which will be used to send an email to the user about their newly created account. This job class is shown below.
Imports System.Net.Mail
Imports Microsoft.WindowsAzure
Public Class tjEmail
Inherits tjob
' a sample email job
Private sample = <job type="email" userid="102">
<email from="noreply@tyranntrpg.org" type="newuser"/>
</job>
' setup extra information required by this job
Private _from As String
Private _emailType As String
' The is the from email address
Public WriteOnly Property From As String
Set(value As String)
_from = value
End Set
End Property
' This will be the email type e.g. newuser
Public WriteOnly Property EmailType As String
Set(value As String)
_emailType = value
End Set
End Property
' If the job XML already exists this will set up
' the information automatically
Public Sub New(jobStr As String)
MyBase.new(jobStr)
Dim jobXML As XElement = XElement.Parse(jobStr)
_from = jobXML...<email>.@from
_emailType = jobXML...<email>.@type
End Sub
' Create an empty email job if creating a new job
Public Sub New()
MyBase.New()
JobType = "email"
_from = "noreply@tyranntrpg.org"
_emailType = ""
End Sub
'' Send the email
Public Overrides Function Process() As Boolean
Dim email As MailMessage
' Generate the correct body of the email
Select Case _emailType
Case "NewAccount"
email = GenerateNewUserEmail()
Case Else
ErrorMessage = String.Format("Email Type [{0}] not recognised", _emailType)
ReportError("tjEmail Process")
Return False
End Select
Dim smtp = New SmtpClient(CloudConfigurationManager.GetSetting("SMTPAddress"), Integer.Parse(CloudConfigurationManager.GetSetting("SMTPPort")))
smtp.Credentials = New Net.NetworkCredential(CloudConfigurationManager.GetSetting("SMTPUser"), CloudConfigurationManager.GetSetting("SMTPPassword"))
smtp.EnableSsl = True
Try
smtp.Send(email)
Catch ex As Exception
Me.ErrorMessage = ex.Message
ReportError("tjEmail Send()")
Return False
End Try
Return True
End Function
' This will generate the subject and body of the newuser email
Private Function GenerateNewUserEmail() As MailMessage
If _usr Is Nothing Then
ErrorMessage = "_usr is null"
ReportError("GenerateNewUserEmail()")
Return Nothing
End If
Dim email As New MailMessage(_from, _usr.EmailAddress)
Dim subject As String = ""
Dim body As String = ""
Try
Dim emailMsg = (From e In _db.EmailMessages
Where e.Name = "NewAccount"
Select e).Single
subject = emailMsg.Subject
body = String.Format(emailMsg.Body, _usr.Name)
Catch ex As Exception
ErrorMessage = ex.Message
ReportError("GenerateNewUserEmail(), Lynq Query to _db.EmailMessages")
Return Nothing
End Try
ErrorMessage = body
email.Subject = subject
email.Body = body
Return email
End Function
Public Overrides Function ToXML() As XElement
Return <job type="email" userid=<%= UserID %>>
<email from=<%= _from %> type=<%= _emailType %>/>
</job>
End Function
Private Sub Smtp_SendCompleted(sender As Object, e As ComponentModel.AsyncCompletedEventArgs)
If e.Error Is Nothing Then
ErrorMessage = "Mail sent correctly"
Me.ReportError("tjEmail SendCompleted")
Else
ErrorMessage = e.Error.Message
Me.ReportError("tjEmail SendCompleted")
End If
End Sub
End Class
All the SMTP server information is held in the azure service configuration file. Again the overrode functions do all the work in this class.
Now onto the back end. This is uses the Worker Role With Service Bus Queue template (Shown Below)

As the job classes actually do all the work required, this is a very small piece of code.
Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Linq
Imports System.Net
Imports System.Threading
Imports Microsoft.ServiceBus
Imports Microsoft.ServiceBus.Messaging
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.Diagnostics
Imports Microsoft.WindowsAzure.ServiceRuntime
Imports Microsoft.WindowsAzure.Storage
Imports Tyrannt.Domain
Public Class WorkerRole
Inherits RoleEntryPoint
' The name of your queue
Const QueueName As String = "standard_queue"
Private _db As New TyranntDb
' QueueClient is Thread-safe. Recommended that you cache
' rather than recreating it on every request
Dim Client As QueueClient
Dim IsStopped As Boolean
Public Overrides Sub Run()
' This is a sample implementation for Tyrannt.Backoffice. Replace with your logic.
While (Not IsStopped)
Try
' Receive the message
Dim receivedMessage As BrokeredMessage = Client.Receive()
If (Not receivedMessage Is Nothing) Then
' Process the message
Trace.WriteLine("Procesing", receivedMessage.SequenceNumber.ToString())
ProcessMessage(receivedMessage)
receivedMessage.Complete()
End If
Catch ex As MessagingException
If (Not ex.IsTransient) Then
Trace.WriteLine(ex.Message)
Throw ex
End If
Thread.Sleep(10000)
Catch ex As OperationCanceledException
If (Not IsStopped) Then
Trace.WriteLine(ex.Message)
Throw ex
End If
End Try
End While
End Sub
Private Sub ProcessMessage(msg As BrokeredMessage)
Dim msgBody As String = "msgBody not available"
Dim errMsg As String = ""
Try
msgBody = msg.GetBody(Of String)()
' Turn the message into an XML element
Dim xmlMsg As XElement = XElement.Parse(msgBody)
' Extract the message type from the element
Dim type As String = xmlMsg.@type
' Now we create a job
Dim job As tjob
'ReportError("Processing job [" & type & "]", "WorkerRole.vb")
Select Case type
' Use the message type to see what kind of job is required
Case "newuser"
job = New tjNewUser(xmlMsg.ToString)
Case "email"
job = New tjEmail(xmlMsg.ToString)
Case Else
ReportError("job type [" + type + "] Not recognised", "WorkerRole.ProcessMessage()")
Exit Sub
End Select
' Process the job.
If job.Process() = True Then
' The job succeeded so write a trace message to say this and
' delete the message from the queue.
Trace.WriteLine(String.Format("{0} succeeded", type), "Information")
Else
' The job failed so write a trace error message saying why the job failed.
' This will leave the job on the queue to be processed again.
Trace.WriteLine(String.Format("{0} failed: {1} ", type, job.ErrorMessage), "Error")
End If
Catch ex As Exception
' something big has gone wrong so write this out as an error trace message.
Trace.WriteLine(String.Format("Failed to parse xml message: [{0}]", msgBody, "Error"))
ReportError(ex.Message, "WorkerRole.ProcessMessage()")
Exit Sub
End Try
End Sub
Public Overrides Function OnStart() As Boolean
' Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12
' Create the queue if it does not exist already
Dim connectionString As String = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString")
Dim namespaceManager As NamespaceManager = namespaceManager.CreateFromConnectionString(connectionString)
If (Not namespaceManager.QueueExists(QueueName)) Then
namespaceManager.CreateQueue(QueueName)
End If
' Get a client to use the queue
Client = QueueClient.CreateFromConnectionString(connectionString, QueueName)
IsStopped = False
Return MyBase.OnStart()
End Function
Public Overrides Sub OnStop()
' Close the connection to Service Bus Queue
IsStopped = True
Client.Close()
MyBase.OnStop()
End Sub
Private Sub ReportError(msg As String, location As String)
Dim err As New ErrorMessage
err.ErrorTime = DateTime.Now
err.Location = location
err.Message = msg
_db.ErrorMessages.Add(err)
_db.SaveChanges()
End Sub
End Class
I’ve added the ability to access the database to this class only for error reporting which helps tracking down any issues during development. The only subroutine aside from the error routines is the ProcessMessage. This function will get the message body. Work out what type of message it is and then generate the correct class and run the Process method. (Note: You can only use the GetBody method once, if you try more than once it will crash out.) In this example I complete the received message even if it fails to process (Failure gets added to the error table). In future I may end up doing more elaborate things.
I hope some people find this helpful.
2012/10/30
Accessing and OData Feed in WinRT App using Visual Basic.NET
As I’ve been searching the internet to try and find ways of accessing OData Feeds in visual basic .net and not finding much help I thought I’d put together a small example to help others.
This will use a demo OData feed to pull customer records from an example banking application. The feed is located in an Azure Cloud Service and is accessed by the following URL
http://t24irisdemo.cloudapp.net/tbank/Wealth.svc/
(This feed may be removed in the future)
The first thing to do is create a Visual Basic Blank Windows Store XAML application.
Also add a basic page called CustomerPage.
Add a Service Reference which points to the above URL
We will only use Customers from the feed.
First display all the customers. To do this we need to add a Gridview to the main page XAML with an Item Template to display customer information. (I’m not a designer so I apologies for the rough look of the details 😉 )
<GridView x:Name="defaultGridView" Grid.Row="1" Tapped="defaultGridView_Tapped_1" Margin="10" BorderBrush="#FFF7EC05" BorderThickness="1" FontFamily="Global User Interface" >
<GridView.ItemTemplate>
<DataTemplate>
<Grid Width="250" Height="135" Background="#002">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Text="{Binding Path=CustomerCode}" HorizontalAlignment="Center" FontWeight="ExtraBold" ></TextBlock>
<Image Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Source="{Binding Path=Photo}" Stretch="Uniform" Margin="4" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="1" Grid.Row="1" FontStyle="Italic" VerticalAlignment="Bottom">Name:</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding Path=Name}" VerticalAlignment="Top" HorizontalAlignment="Left" TextWrapping="Wrap"></TextBlock>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Next add the following code to the code behind for the Main Page.
Imports System.Data.Services.Client
' The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
''' <summary>
''' An empty page that can be used on its own or navigated to within a Frame.
''' </summary>
Public NotInheritable Class MainPage
Inherits Page
Private Const IRISURL As String = "http://t24irisdemo.cloudapp.net/tbank/Wealth.svc"
Private _context As New irisServiceReference.T24Wealth(New Uri(IRISURL))
Private WithEvents _allCust As DataServiceCollection(Of irisServiceReference.Customers)
''' <summary>
''' Invoked when this page is about to be displayed in a Frame.
''' </summary>
''' <param name="e">Event data that describes how this page was reached. The Parameter
''' property is typically used to configure the page.</param>
Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
_allCust = New DataServiceCollection(Of irisServiceReference.Customers)
AddHandler _allCust.LoadCompleted, AddressOf LoadAllCustComplete
Dim query = _context.Customers
_allCust.LoadAsync(query)
End Sub
Private Sub LoadAllCustComplete(sender As Object, e As LoadCompletedEventArgs)
defaultGridView.ItemsSource = _allCust
End Sub
Private Sub defaultGridView_Tapped_1(sender As Object, e As TappedRoutedEventArgs)
Dim gv As GridView = sender
Dim item As irisServiceReference.Customers = gv.SelectedItem
Dim customerNo As String = customerNo = item.CustomerCode
Frame.Navigate(GetType(CustomerPage), customerNo)
End Sub
End Class
The above code will pull all the customers from the feed and then bind them to the GridView. There is also a Tap event handler which will pull the customer number from the grid view item and navigate to the customer view page (I’ll leave this to you to have a go a designing the look and feel. In the following code example I assumed there is a text block called customerNameTextBlock).
If you put a debug point at the _allCust.LoadAsync(query) line. You will notice that Query is turned into a URL to the feed.
query = {http://t24irisdemo.cloudapp.net/tbank/Wealth.svc/Customers}
Next we need to put code behind the Customer info page to pull the tapped customer details back so that we can populate this page. The following code with do this.
' The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237
Imports System.Data.Services.Client
''' <summary>
''' A basic page that provides characteristics common to most applications.
''' </summary>
Public NotInheritable Class CustomerPage
Inherits Common.LayoutAwarePage
Private Const IRISURL As String = "http://t24irisdemo.cloudapp.net/tbank/Wealth.svc"
Private _context As New irisServiceReference.T24Wealth(New Uri(IRISURL))
Private WithEvents _allCust As DataServiceCollection(Of irisServiceReference.Customers)
''' <summary>
''' Populates the page with content passed during navigation. Any saved state is also
''' provided when recreating a page from a prior session.
''' </summary>
''' <param name="navigationParameter">The parameter value passed to
''' <see cref="Frame.Navigate"/> when this page was initially requested.
''' </param>
''' <param name="pageState">A dictionary of state preserved by this page during an earlier
''' session. This will be null the first time a page is visited.</param>
Protected Overrides Sub LoadState(navigationParameter As Object, pageState As Dictionary(Of String, Object))
End Sub
''' <summary>
''' Preserves state associated with this page in case the application is suspended or the
''' page is discarded from the navigation cache. Values must conform to the serialization
''' requirements of <see cref="Common.SuspensionManager.SessionState"/>.
''' </summary>
''' <param name="pageState">An empty dictionary to be populated with serializable state.</param>
Protected Overrides Sub SaveState(pageState As Dictionary(Of String, Object))
End Sub
Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
Dim customerNo As String = e.Parameter
pageTitle.Text = "Customer " + customerNo
_allCust = New DataServiceCollection(Of irisServiceReference.Customers)
AddHandler _allCust.LoadCompleted, AddressOf allCustomerLoadCompleted
Dim query = From c In _context.Customers
Where c.CustomerCode = customerNo
Select c
_allCust.LoadAsync(query)
End Sub
Private Sub allCustomerLoadCompleted(sender As Object, e As LoadCompletedEventArgs)
customerNameTextBlock.Text = _allCust.Single.Name
End Sub
End Class
You’ll notice in this piece of code that a Linq query is used to pull the customer record from the feed. If you again put a debug point at _allCust.LoadAsync(query) you will notice the query is also transformed into a OData URL.
query = {http://t24irisdemo.cloudapp.net/tbank/Wealth.svc/Customers()?$filter=CustomerCode eq '100320'}
Hopefully this very simple example will help you get kick-started in developing WinRT applications that access OData feed.
2012/09/21
Sort of found the answer to my sharing issue with WinRT & Dropbox
It turns out, if you have the same folder location on two different machines. E.g. the default drop box location c:\users\{user}\dropbox, Â this makes the project fail to run on a the machine that wasn’t the one it was created on.
If you move the dropbox folder to a new location on the other machine, E.g. c:\Dropbox then it will run with no issues. A weird one this, no one had an answer to why it happens.
Hopefully if someone else has this issue, then this post may help them.
BTW: The person at the Workshop seemed to be taken aback that the app was written using VB too 😉
