VB Magic

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)
New Worker Role Dialogue

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.

2011/08/10

Azure Queues

I’ve been learning about using Queues in Azure and thought the best way to learn was to start writing an application to test this. My example site will probably only run on single instances; but I decided from the start to write something that will be scalable so came up with the following principle

The client/website can only READ from the database. Anything that requires updating the database must be done via the back end. The client/website must be able to cope with the fact that the requested update will not happen immediately.

The upshot of this, I came up with a job system that uses Azure queues to pass work to the back end worker role. For the moment, I decided to have one queue which handled multiple types of jobs. This lead to me creating a job base class which will cover the common things that the different jobs required. I also decided to use an XML format to describe the job. I need to make sure though that the jobs do not grow bigger than the size allowed for an Azure Queue job (8k).

Below is the base class which is inherited in the actual job classes:

Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.StorageClient
Imports Microsoft.WindowsAzure.ServiceRuntime

Public MustInherit Class tjob

    ' Enable database access
    Protected Friend _db As New TyranntDB
    ' A place holder for the user information
    Protected Friend _usr As tUser
    ' setup variables to allow for access to Azure Queues
    Protected Friend _storageAccount As CloudStorageAccount
    Protected Friend _jobQueue As CloudQueue

    ' A sample of a job entry in the Queue
    Private sample = <job type="email" user="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
        UserID = Convert.ToInt64(usrstr)
    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.users
              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.
        _storageAccount = CloudStorageAccount.Parse( RoleEnvironment.GetConfigurationSettingValue(TyranntSupport.Constants.STORAGE_CONNECTION))
        ' Now get the queue client.
        Dim client As CloudQueueClient = _storageAccount.CreateCloudQueueClient
        _jobQueue = client.GetQueueReference(Constants.QUEUE_JOBS)
        ' Create the queue if it doesn't exist.
        _jobQueue.CreateIfNotExist()
        Try
            ' Now add the job details to the queue.
            Dim msg As New CloudQueueMessage(Me.ToString)
            _jobQueue.AddMessage(msg)
        Catch ex As Exception
            _ErrorMessage = ex.Message
        End Try
    End Sub
End Class

The TyranntDB class is an Entity Framework code first class that allows access to the SQL Azure database. Any class prefixed with a t (E.g. tUser) is a database table class. You will notice a Sample variable using XML Literals. This is just as an example to show how that job is formed in XML. Each inherited class will have it’s own sample.

All job classes need to be able to add themselves to the Azure Queue. They also need to be able to access the SQL Azure database. These features were written into the base class. All job classes must also have a way of being “Processed” which meant adding a MustOverride function called Process. They must also be able to export themselves as an XML document which is why the MustOverride function called ToXML is added.

The website uses Forms Authentication to enable it to validate users. But I also have a site specific user table to add extra information too. As this involves updating the database, this is the first “Job” that needs creating:

Public Class tjNewUser
    Inherits tjob

    ' an example of a new user job
    Private sample = <job type="newuser" user="-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

        Dim r = From u In _db.users
              Where u.username = _userName
              Select u

        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.
            Return True
        End If
        ' create a new user
        Dim usr As New tUser
        ' populate the generic information
        usr.username = _userName
        usr.email = _email
        usr.fullname = _fullName
        ' now set the user group to be member
        Try
            Dim grp As tUserGroup = _db.GetUserGroup("member")

            If IsNothing(grp) Then
                _db.CreateBaseGroups()
                usr.usergroup = _db.GetUserGroup("member")
            Else
                usr.usergroup = grp
            End If
        Catch ex As Exception
            ErrorMessage = ex.Message
            Return False
        End Try

        ' now save the user
        Try
            _db.users.Add(usr)
            _db.SaveChanges()
        Catch ex As Exception
            ErrorMessage = ex.Message
            Return False
        End Try

        ' Now that the user was sucessfully created,
        ' generate a new user email job
        Dim jb As New tjEmail
        jb.EmailType = "newuser"
        jb.From = "mail@me.uk"
        jb.UserID = usr.ID
        ' Add the job to the Azure job queue
        jb.AddJobToQueue()
        If jb.ErrorMessage = "" Then
            Return True
        Else
            ErrorMessage = jb.ErrorMessage
            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

I’ve added the extra properties required for adding a new user and the extraction of these properties from the XML. The process function is also created which will create the user row in the users table. Hopefully the comments in the code should explain the process to do this. This routine also makes use of XML Literals which is a VB only thing at time of writing. (For example used in the ToXML and New functions.

As you can see at the end of the processing, we need to send a confirmation email to the user who has created the account. This kind of thing is also ideal for the back end to deal with hence being handled by the job queue system:

Imports System.Net.Mail

Public Class tjEmail
    Inherits tjob

    ' a sample email job
    Private sample = <job type="email" user="102">
                         <email from="mail@me.uk" 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 = ""
        _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 "newuser"
                email = GenerateNewUserEmail()
            Case Else
                ErrorMessage = String.Format("Email Type [{0}] not recognised", _emailType)
                Return False
        End Select

        ' Now set up the SMTP server client to send the email.
        Dim smtp As New SmtpClient(My.Resources.smtpServer, Integer.Parse(My.Resources.smtpPort))
        ' Pull the smtp login details.
        smtp.Credentials = New Net.NetworkCredential(My.Resources.smtpUser, My.Resources.smtpPass)
        Try
            smtp.Send(email)
        Catch ex As Exception
            ErrorMessage = ex.Message
            Return False
        End Try
        Return True
    End Function

    ' This will generate the subject and body of the newuser email
    Private Function GenerateNewUserEmail() As MailMessage
        Dim email As New MailMessage(_from, _usr.email)
        email.Subject = My.Resources.Resources.TyranntAccountCreated
        email.BodyEncoding = System.Text.Encoding.Unicode
        email.IsBodyHtml = False
        email.Body = String.Format(My.Resources.newUserEmail, _usr.username)
        Return email
    End Function

    Public Overrides Function ToXML() As XElement
        Return <job type="email" userid=<%= UserID %>>
                   <email from=<%= _from %> type=<%= _emailType %>/>
               </job>
    End Function

End Class

The process function in this job will generate an email and pull the smtp server details out of a resource file and depending on the type of email, will take the email body from resources too.

Now these job classes are created, they can be added to the job queue by using {job}.AddJobToQueue() method as shown in the tjNewUser class. But how will they be processed. This is where the WorkerRole comes into play. As all the work is done by the job classes themselves, only a very simple piece of code is required to process the queues:

    Public Overrides Sub Run()

        Trace.WriteLine("TyranntDogsbody entry point called.", "Information")

        ' Loop forever
        While (True)
            ' Get the next message from the queue
            Dim msg As CloudQueueMessage = Nothing
            msg = _jobQueue.GetMessage(TimeSpan.FromSeconds(30))

            If IsNothing(msg) Then
                ' If message doesn't exist then seep for 1 minute
                Thread.Sleep(60000)
            Else
                ' Message exists so process the message
                ProcessMessage(msg)
            End If
        End While
    End Sub

    Private Sub ProcessMessage(msg As CloudQueueMessage)

        Try
            ' Turn the message into an XML element
            Dim xmlMsg As XElement = XElement.Parse(msg.AsString)
            ' Extract the message type from the element
            Dim type As String = xmlMsg.@type

            ' Now we create a job
            Dim job As tjob
            Select Case type
                ' Use the message type to see what kind of job is required
                Case "newuser"
                    job = New tjNewUser(msg.AsString)
                Case "email"
                    job = New tjEmail(msg.AsString)
                Case Else
                    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")
                _jobQueue.DeleteMessage(msg)
            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}]", msg.AsString), "Error")
            Exit Sub
        End Try
    End Sub

As you can see from the above code. There very little extra code required to process the job as all the work is done inside the job class.

This may be refined in the future but I hope it’s helpful for some people.

Jas

2011/04/18

Quick post about XML Literals

Filed under: .NET, VB.NET — Tags: , , — vbmagic @ 9:41 pm

Just a quick post as I’ve decided to write a simplified standalone version of a game to get some game mechanics designed. As a way of storing information that would have been stored I though I would use XML for the “Static” information which brings me to the great XML literals addition to vb.NET. Makes the code so much more readable and it also allows auto complete and error checking while you type it.

Below is a snippet of code to give an example of how the data is generated.


        Dim classes As XElement =
            <classes>
                <class description="A Character trained in weapons and armour">Fighter</class>
                <class description="A Character trained in Ranged and close quarters combat">Rogue</class>
                <class description="A Character trained in the arcane arts of combat">Mage</class>
                <class description="A Character trained in the arcane arts of healing and defence">Priest</class>
            </classes>

        Dim mobs As XElement =
            <mobs>
                <mob strength="1d4+2" constitution="1d6" dexterity="1d4" intelligence="3"
                    weapon="Rats Teeth">Giant Rat</mob>
            </mobs>

        Dim items As XElement =
            <items>
                <item type="weapon" value="1d4" action="stabs">Dagger</item>
                <item type="weapon" value="1d4+1" action="strikes">Staff</item>
                <item type="weapon" value="1d6" action="Shoots">Bow</item>
                <item type="weapon" value="1d6" action="swings">Short Sword</item>
                <item type="defence" value="1" action="">Robes</item>
                <item type="defence" value="2" action="">Leather Armour</item>
                <item type="defence" value="1d4-2" action="">Shield</item>
                <item type="potion" value="1d4" action="quaffs" stat="mana">Mana Potion</item>
                <item type="potion" value="1d4" action="quaffs" stat="hp">Health Potion</item>

                <item type="weapon" value="1d4-1" action="bites">Rats Teeth</item>
            </items>

        Dim spells As XElement =
            <spells>
                <spell></spell>
            </spells>

        Dim maps As XElement =
            <maps>
                <map name="test" rows="10" cols="10">
                    <row number="0">##########</row>
                    <row number="1">#*#f...g>#</row>
                    <row number="2">#.#.######</row>
                    <row number="3">#a#.#....#</row>
                    <row number="4">#+#.#.##.#</row>
                    <row number="5">#.#..e##.#</row>
                    <row number="6">#.######.#</row>
                    <row number="7">#.#....+d#</row>
                    <row number="8">#b+c...###</row>
                    <row number="9">##########</row>
                    <event type="message" description="As you were walking along the field, the ground shook and split appart and you landed in what looks to be an old mine tunnel">*</event>
                    <event type="message" description="You notice bones on the floor">a</event>
                    <event type="message" description="You hear scratching noises through the door">b</event>
                    <event type="fight" description="Giant rats run at you as you enter the room" mob="Giant Rat" mobcount="1d4">c</event>
                    <event type="item" description="You find a health potion on the floor" item="Health Potion" itemcount="1">d</event>
                    <event type="fight" description="Giant Rats swarm round the corrner of the tunnel" mob="Giant Rat" mobcount="1d4+4">e</event>
                    <event type="message" description="You feel a breeze from the end of the corridor">f</event>
                    <event type="message" description="You see a ladder ahead leading out of the tunnel">g</event>
                </map>
            </maps>

2011/04/04

Dynamically creating pivot items and populating with data

Filed under: Learning — Tags: , , , , , , , — vbmagic @ 5:01 pm

I’ve spent the weekend trying to work out how to create the pages you see on a Pivot Page dynamically through code. It took a lot of web searching and trial and errors but I seem to have managed to get the basics of this worked out. I’m posting this to help other people who are trying to do the same thing. In this case it works, but if anyone else has a better/more efficient way of doing this I would be interested in looking at it.

The database/Web server has a directory full of images which the web service will pass back to the client using XML. Each image can belong to a image type. Each of these types will be a Pivot Item in the Pivot page. The list of images for each of these types will be displayed on each of these Pivot items.

I started by creating a pivot page and deleted any pivot items that where in the XAML. I then created the following Page Load event code:

    Private _client As New adminServiceReference.AdminServiceClient

    Private Sub PhoneApplicationPage_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
        ' setup the event handlers for retrieving image types and image lists
        AddHandler _client.GetImageTypesCompleted, AddressOf client_GetImageTypesCompleted
        AddHandler _client.GetImagesCompleted, AddressOf client_GetImagesCompleted

        ' request the image types
        Dim str As New StrType
        str.TokenGUID = GetToken()
        _client.GetImageTypesAsync(str)
    End Sub

    Private Function GetToken() As Guid
        Dim result As Guid
        If _phoneAppService.State.TryGetValue("token", result) Then
            Return result
        Else
            MessageBox.Show("Failed to retrieve token")
            Return Nothing
        End If
    End Function

This will call the GetImageTypes web service on the server (I will document these in another post)
Once the request has been completed, all the image types will be sent back to the client in XML format.

    ' Routine to handle the Get Image Types completed event.
    Private Sub client_GetImageTypesCompleted(ByVal sender As Object, ByVal e As adminServiceReference.GetImageTypesCompletedEventArgs)
        If e.Error Is Nothing Then
            Dim types = e.Result
            ' did the server side report an error?
            If types.ErrorMessage = "" Then
                ' no so loop through each Image type in the xml result
                For Each ele In types.xml...<type>
                    ' We need to create a new Pivot item for each image type
                    Dim p As New PivotItem
                    ' set the header name to the lowercase version of the type name
                    ' this is to fit in with the standard way of showing pivot items
                    p.Header = ele.Value.ToLower
                    ' add the pivot item to the pivot page
                    imagePivotControl.Items.Add(p)
                    ' now request all the images under of that type
                    Dim str As New StrType
                    str.text = ele.Value
                    _client.GetImagesAsync(str)
                Next
            Else
                ' The server side got an error so display the error
                ' to aid in debugging.
                MessageBox.Show(types.ErrorMessage)
            End If
        Else
            ' the request to the webservice caused an error so
            ' display this error to try and help debug the problem.
            MessageBox.Show(e.Error.Message)
        End If
    End Sub

The comments in the above code should lead you through the creation and titling of the PivotItem and used XML Literals a very handy feature in VB to make code more readable when interacting with XML. (This should be implemented in C#)

For each image type pivot item created it will also request all the images for that image type which the following code will handle.

    ' a routine to handle the Get Images completed event.
    Private Sub client_GetImagesCompleted(ByVal sender As Object, ByVal e As adminServiceReference.GetImagesCompletedEventArgs)
        ' Did the request cause an error
        If e.Error Is Nothing Then
            Dim imgs = e.Result
            ' Did the web server get an error while processing the request.
            If imgs.ErrorMessage = "" Then
                ' no error so start searching the PivotItems to find the one matching
                ' the type that we requested the images for.
                For Each pg As PivotItem In imagePivotControl.Items
                    If pg.Header.ToString = imgs.type.ToLower Then
                        ' We have a match so create a listbox item to hold the details
                        Dim lb As New ListBox
                        ' we need a counter to display next to the image
                        ' this is only temorary until we have an image there
                        Dim cnt As Integer = 0
                        ' Loop through all the image elements in the returned XML
                        For Each ele In imgs.xml...<image>
                            ' We need to create a grid to store the information in the
                            ' list box item this is the equivilent to the following XAML
                            '
                            '   <grid>
                            '       <Grid.RowDefinitions>
                            '           <RowDefinition Height="Auto"/>
                            '       </Grid.RowDefinitions>
                            '       <Grid.ColDefinitions>
                            '           <ColDefinition Width="100"/>
                            '           <ColDefinition Width="Auto"/>
                            '       </Grid.RowDefinitions>
                            '    </grid>

                            ' Create the grid object
                            Dim grd As New Grid
                            ' Create the row definition objec
                            Dim rowdef As New RowDefinition
                            ' the row definition object uses a object called gridlength
                            ' we create one of these and set it to Auto
                            Dim glen As New GridLength
                            glen = GridLength.Auto
                            ' we add the height information to the row definition
                            rowdef.Height = glen
                            ' now we add the row definition to the list of row definitions
                            ' in the grid object
                            grd.RowDefinitions.Add(rowdef)
                            ' Now we need to create a column definition
                            Dim coldef As New ColumnDefinition
                            ' the first column has a width of 100 pixels for now, it will
                            ' contain an image eventually and we can leave it set to auto.
                            coldef.Width = New GridLength(100)
                            ' add the column definition to the list of column definitions in
                            ' the grid object.
                            grd.ColumnDefinitions.Add(coldef)
                            ' we now create the second column definition to auto which is the
                            ' same as we used with the row definiton so lets just re-use
                            ' this object.
                            coldef = New ColumnDefinition
                            coldef.Width = glen
                            ' add the final column definition to the collection of column
                            ' definitions in the grid object.
                            grd.ColumnDefinitions.Add(coldef)

                            ' Now we start to add the data, first we create a textblock that
                            ' will be used to hold the count.
                            ' This is the equivilent to the following XAML.
                            '
                            '   <TextBox Grid.Column="0" Grid.Row="0" Text="{cnt.string}"/>
                            '
                            Dim tb = New TextBlock
                            ' we set the row and column of the textblock to make it appear in
                            ' the correct loctation
                            Grid.SetColumn(tb, 0)
                            Grid.SetRow(tb, 0)
                            ' set the text property of the text block to the current count
                            tb.Text = cnt.ToString
                            ' add the textblock to the children objects of the grid.
                            grd.Children.Add(tb)
                            ' Now we add a new text block which will display the name of the
                            ' image file on the server.
                            ' This is the equivilent to the following XAML
                            '
                            '   <TextBox Grid.Column="1" Grid.Row="0" Text="{ele.value}"/>
                            '
                            tb = New TextBlock
                            ' set the row and column information of the text block.
                            Grid.SetColumn(tb, 1)
                            Grid.SetRow(tb, 0)
                            ' pull the content of the image item ( <image>value</image> ) and
                            ' put this into the text property of the text block
                            tb.Text = ele.Value
                            ' add the text block to the child objects of the grid.
                            grd.Children.Add(tb)

                            ' Now we create the list box item to add to the list box in
                            ' the pivot item.
                            Dim lbi As New ListBoxItem
                            ' add the grid to the listbox item
                            lbi.Content = grd
                            ' and add the list box item to the list box
                            lb.Items.Add(lbi)
                            ' increment the counter
                            cnt += 1
                        Next
                        ' add the list box to the pivot item.
                        pg.Content = lb
                    End If
                Next
            Else
                ' the server produced and error so display to aid in debugging
                MessageBox.Show(imgs.ErrorMessage)
            End If
        Else
            ' the request caused and error so display this to aid in debugging
            MessageBox.Show(e.Error.Message)
        End If
    End Sub

Again I have commented the code to lead you through how the listbox and listbox items are created from the XML results and used to populate the pivot item for each image type.

Jas

Blog at WordPress.com.