VB Magic

2012/12/03

Decisions Decisions – Solution Structure and how to get things done

Filed under: .NET, Azure, Learning — Tags: , , , , , , , — vbmagic @ 2:44 pm

I spent most of this weekend, researching different ways to achieve my project. There are certain things that I know I want to do:

Adopt a Three Tier Approach.

  1. Multiple Clients (Including a Website)
  2. A middle service layer that all the clients talk to which can read from the database but not update.
  3. A back end which is the only part of the solution allowed to update the database

The communication between the middle tier and the back end tier will be via the Azure Service bus.

I want the user of the application to be able to log in to the various client using their Windows Account/Google/Facebook ID’s.

With this in mind I was looking into ways to achieve these goals. I was mainly concentrating on what the middle service tier should be.

I wanted something that will make it easy to implement clients on non Microsoft platforms which drew me towards using the new ASP.Net MVC Web API. The downside of this is there is no metadata to describe the service which makes the client much harder to write. After a few discussions with a colleague, they suggested using WCF OData service which should allow making clients easy on the windows side. (Automatic generation of model classes when adding a service reference)

But I’m still open to suggestions from anyone reading this if they think there may be a better way.

The solution structure for the project as it stands now will be:

Tyrannt.Model (Holds the Code First EF Classes)

Tyrannt.Backoffice (Windows Azure Worker Role – The back end tier)

Tyrannt.OData (Web Role – the Middle WCF OData tier)

Tyrannt.Website (First client – will consume the OData service)

I would also like to make use of database migrations that are in Entity Framework 5. Which also brings up another question which it’s been hard to answer. Where is the best place to put the Database context. An easy solution would be to put one in both the Backoffice and OData projects but that means maintaining two separate classes that basically do the same job)

I’ve seen some posts about not having the database context in the same project as the Model classes. I’m trying to work out the best way to do this and still allow database migrations.

Feel free to comment this posts with suggestions ideas or even telling me that I’ve got it all wrong ;-).

2012/11/28

Adding in an Extra Tier

Filed under: .NET, ASP.NET MVC, Learning — Tags: — vbmagic @ 10:16 am

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/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)
Dragon and Warrior

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

Filed under: .NET, ASP.NET MVC — Tags: , , , , , — vbmagic @ 6:13 pm

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

Filed under: .NET, ASP.NET MVC, VB.NET — Tags: , , , — vbmagic @ 4:10 pm

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

Parsing the HTTPResponse from the Azure Management API

Filed under: .NET, Azure, VB.NET — Tags: , , , , , — vbmagic @ 5:50 pm

Ok, after that last example I ended up pulling what hair I had left in my head out trying to parse the XML that got returned. I have come across this problem before but my memory being what it is I had totally forgotten about it so have written this blog post to help remind myself next time this happens.

I couldn’t seem to be able to get at the elements in the data. I was doing this:

            ' Parse the web response
            responseStream = httpResponse.GetResponseStream
            reader = New StreamReader(responseStream)
            Dim rawOutput As String = reader.ReadToEnd
            Dim xdoc As XDocument = XDocument.Parse(rawOutput)

            Dim serviceOperations = From s In xdoc.Descendants("SubscriptionOperation")
                                    Select s


            For Each s In serviceOperations
                Dim email = s.Descendants("UserEmailAddress").Value
                Dim operation = s.Descendants("OperationName").Value
                Dim opDate = s.Descendants("OperationStartedTime").Value
                Console.WriteLine(String.Format("User: {0} Did: {1} On: {2}", email, operation, opDate))
            Next

And stepping through the code I found that ServiceOperations resolved to nothing. After a couple of hours of poking and prodding and then searching the internet I came across the solution in this post: Fetching Hosting Services Name Using Windows Azure Management API

Namespaces! Memory starts ticking over and I remember hitting this before. So here is the revised code (In full) which will get Subscription details (Basically audit information) from the Management API

Imports System.Net
Imports System.Security.Cryptography.X509Certificates
Imports System.IO
Imports System.Text

Module Module1

    Sub Main()
        ' 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

        Try

            ' specify time range
            Dim startTime As String = "2012-11-11"
            Dim endTime As String = "2012-11-13"

            ' The ID for the Windows Azure subscription.
            Dim subscriptionId As String = "{Your Subscription}"

            ' 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 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}/operations?StartTime={1}&EndTime={2}",
                              subscriptionId, startTime, endTime)
                )

            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", "2012-03-01")
            httpRequest.ContentType = "application/xml"

            ' 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)
            Dim rawOutput As String = reader.ReadToEnd
            Dim ns As XNamespace = "http://schemas.microsoft.com/windowsazure"

            Dim xdoc As XDocument = XDocument.Parse(rawOutput)

            Dim serviceOperations = From s In xdoc.Descendants(ns + "SubscriptionOperation")
                                    Select s


            For Each s In serviceOperations
                Dim email = s.Descendants(ns + "UserEmailAddress").Value
                Dim operation = s.Descendants(ns + "OperationName").Value
                Dim opDate = s.Descendants(ns + "OperationStartedTime").Value
                Console.WriteLine(String.Format("User: {0} Did: {1} On: {2}", email, operation, opDate))
            Next

            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

This can be used as an example to pull audit information from Azure. I hope this helps others not make my silly mistake 😉

P.s. I hope you like the new theme. Thanks to the above blog post (Debugmode.net) for showing it to me. It makes reading source code much better on wider monitors.

A VB example to access the azure management REST API

Filed under: .NET, Azure, VB.NET — Tags: , , , — vbmagic @ 11:54 am

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)
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.

2012/10/30

Accessing and OData Feed in WinRT App using Visual Basic.NET

Filed under: .NET, VB.NET, Windows 8, Windows Store App — Tags: , , , — vbmagic @ 5:12 pm

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

Visual Studio Dialog

Add Service Reference

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.

« Newer PostsOlder Posts »

Blog at WordPress.com.