Developing Mobile Applications for DotNetNuke

This past year has seen a lot of activity in the mobile space.  Apple continued its string of blockbuster phone releases with iPhone 4 and added the iPad for a killer combination.  Google continued a blistering pace of release after release for the Android OS, combined with what seems like a constant stream of new phones and tablet devices.  And just recently Microsoft held their official launch for Windows Phone 7. All of these platforms have one thing in common: they are all heavily dependent on mobile applications.

These are but some of the many changes happening in the mobile space this year, but they all point to the same thing: mobile application development is the new gold rush.  Developers who learn to develop mobile applications stand to be in large demand for many years to come as more and more businesses are looking for ways to extend their reach from the web to the phone.  Companies like Facebook, Twitter, NetFlix, IMDB, Amazon, Ebay and many others are finding mobile platforms a great way to reach their target audience.

Many businesses are looking for ways that they can extend their presence to the mobile space.  While some companies are focused on making their websites mobile compatible, most companies who understand the mobile space realize that users prefer native applications that are optimized for the mobile experience.  In fact, the September issue of Wired magazine proclaimed that “The Web is Dead”.  The feature article discussed how more and more, users are flocking to native applications and the internet is just a delivery mechanism for the data that those applications consume.

With all of the different platforms available, which one should you learn?  That is the fundamental question that every developer and company in the mobile space must ask themselves.  Do you learn Objective C (for the iPhone/iPad), or Java (for Android) or C#/Silverlight (for Windows Phone 7)?  This very question was on my mind last year when I stumbled across Appcelerator Titanium.

Appcelerator Titanium offers an alternative to targeting a single mobile device and instead provides a platform for developing mobile applications for multiple devices.  With iPhone, iPad, Android and Blackberry support, Titanium lets you leverage a single codebase to build mobile applications for todays most popular mobile devices.

Titanium has it’s roots in web development and takes advantage of the growing popularity of JavaScript to provide developers with a development experience that is easy to learn.  Titanium includes native libraries for the supported OS’s in order to ensure that your mobile application will look right at home on the target platform and so that your application performance is equal to that of purely native solutions.

There is still a need for businesses to have a robust web presence, for which DotNetNuke is a great solution.  In this article, I will show how you can use Titanium to develop mobile applications that extend the reach of your customer’s DotNetNuke websites.

Getting Started

The first step to developing mobile applications is to install the development environment, including the SDKs for the mobile devices I want to support, as well as Appcelerator Titanium.  There are great getting started guides for Linux, Windows and Mac on the Appcelerator Developer Center.  One thing that you’ll need to keep in mind is that Titanium is not an IDE, so I’ll need a JavaScript editor.  I could use Visual Studio, Eclipse, Aptana or any other text editor.  I have tried a couple of different editors, including Visual Studio, and found that I prefered Eclipse for editing JavaScript.  You are free to use which ever editor you prefer.

Now that I have everything installed, I am ready to start developing my mobile application.  While working on my mobile application, I want to be able to test my application in the appropriate emulator.  Since it would be too expensive for me to own physical devices for each mobile OS version and device, the mobile OS developers have created device emulators which allows me to test my application in a fairly realistic environment.  I’ll use Titanium to compile my application during development and automatically launch the application in the appropriate emulator (Figure 1).

Figure 1: Appcelerator Titanium and Android Emulator

Later on, when I am ready to test my application on a real device, I will use Titanium to push the application to my mobile phone where I can verify that it behaves correctly.  I can provide this version of the application to my beta testers as well so that I can test it out on many different devices before I go live.

Finally, Titanium will also help us with publishing our application to the various vendor app stores and marketplaces.  This can be a confusing step for many first time mobile developers and Titanium does it’s best to make this a little bit easier for the developer.

Introducing dnnNotes

In order to demonstrate building mobile applications we are going to start with a simple DotNetNuke module that allows a user to maintain “notes” on their DotNetNuke site. dnnNotes will mirror some of the basic functionality of EverNote or OneNote and will let you create a list of notes on your website.  This module will expose these notes through a REST based API which will allow us to manage our notes from a mobile application which I’ll build along with the DotNetNuke module.  One of my key goals for this module is to allow each registered user to create and manage their own notes. 

Although dnnNotes is being built as a mobile enabled module, I want to make sure that the module is architected with the future in mind.  DotNetNuke 5.3 introduced the ContentItem repository in order to support categorization and tagging (also known as taxonomy and folksonomy).  I know that these are key features in both EverNote and OneNote and I want to make sure it won’t be too difficult to add these features to dnnNotes in a future release.  ContentItems also allow me to attach my own meta-data to each content item, without requiring me to create my own data tables.  Using ContentItems allows me to focus on my business logic and not on how to store data in the database. 

The dnnNote module architecture (Figure 2) will include a small data layer to accomodate some custom data behaviors which we’ll discuss later.  The business layer will generally talk to the Content Items repository, but in a couple of cases, I’ll need to augment the DotNetNuke API so I’ll create a small data layer to handle those few cases.  My business layer will also need to enforce security so that users will only be able to access their own notes.
 

Figure 2: dnnNotes Module Architecture

With the requirements set, I can begin building out the dnnNotes module.  Since I’ll be using the Content API, I’ll start there and create a simple wrapper around this API that allows me to store, retrieve and delete notes from the content store.

In order to store or retrieve any content items, I’ll want to be able to segregate notes from other types of content in the system.  To do that I have created a simple property which determines if my custom ContentType already exists, and if it doesn’t then it will be automatically created (Listing 1).

Private Function CreateContentType() As Integer
  Dim typeController As New ContentTypeController()
  Dim dnnNotesType As New ContentType() With { _
    .ContentType = NotesContentType}
  Return typeController.AddContentType(dnnNotesType)
End Function

Private ReadOnly Property ContentTypeID As Integer
  Get
    If _contentTypeID <> Null.NullInteger Then
      Return _contentTypeID
    End If

    Dim typeController As New ContentTypeController
    Dim contentTypes = From t In typeController.GetContentTypes() _
      Where t.ContentType = NotesContentType
      Select t
     
    If contentTypes.Count > 0 Then
      Dim ctntType = contentTypes.First
      _contentTypeID = If(ctntType Is Nothing, _
        CreateContentType(),
        ctntType.ContentTypeId)
       
    Else
      _contentTypeID = CreateContentType()
    End If
    Return _contentTypeID
  End Get
End Property

Listing 1: Creating a Custom ContentType

Now that I have a valid ContentType, I can perform all of the other CRUD operations on my ContentItems (Listing 2).

Public Function GetNotesByUser(ByVal UserId As Integer) As IQueryable(Of ContentItem)
  Return From t In CBO.FillQueryable(Of ContentItem)( _
       DataProvider.Instance.GetContentItemsByContentType(ContentTypeID)) _
       Where t.CreatedByUserID = UserId _
       Select t
End Function

Public Function GetNote(ByVal contentItemId As Integer) As ContentItem
  Return Util.GetContentController().GetContentItem(contentItemId)
End Function

Public Function AddNote(ByVal note As String, _
                        ByVal moduleid As Integer, _
                        ByVal tabid As Integer) As Integer
  Dim ci As New ContentItem() With { _
      .Content = note, _
      .ContentTypeId = ContentTypeID, _
      .ContentKey = "", _
      .Indexed = False, _
      .ModuleID = moduleid, _
      .TabID = tabid}

  Return Util.GetContentController.AddContentItem(ci)
End Function

Public Sub UpdateNote(ByVal note As String, _
                      ByVal moduleid As Integer, _
                      ByVal tabid As Integer, _
                      ByVal contentItemID As Integer)
  Dim oldNote As ContentItem = GetNote(contentItemID)
  oldNote.Content = note
  oldNote.ModuleID = moduleid
  oldNote.TabID = tabid

  Util.GetContentController.UpdateContentItem(oldNote)
End Sub

Public Sub DeleteNote(ByVal contentItemID As Integer)
  'TODO: The core API should follow the convention of only requiring an ItemID to delete an Item
  Util.GetContentController.DeleteContentItem(GetNote(contentItemID))
End Sub

Listing 2: CRUD Methods for Managing Notes

There are two items to note here.  Neither of the issues will prevent us from using the Content API, but they do make it a little more cumbersome and are the signs of an immature API that hasn’t had all the kinks worked out yet.

Notice that I didn’t use the Util.GetContentController inside of GetNotesByUser.  Unfortunately, there was an oversight in the API when it was first created.  This will be corrected in DotNetNuke 5.6, so in the meantime, I have created my own stored procedure and the associated dataprovider in order to provide this functionality.  A future version of the module will be able to call a core method on the ContentController using the Util.GetContentController factory method.

The second item of interest is the DeleteNote method.  Notice that I have to retrieve a ContentItem from the database using GetNote() before I can then delete the item.  This is inefficient and does not follow the normal API pattern where the framework usually only requires an ItemID of some sort to delete an entity. 

Now that we have a functioning business layer, I can start coding my UI.  Like most multi-record modules I will have a view control which lists the various notes which I have created.  This screen will list each of the notes with a corresponding edit and delete button.  I’ll also need a button to create new notes.  Both the “edit” and “new” buttons will take me to an edit screen where I can create my note.  We could use jQuery here to make the UI a bit more modern, but for now we will stick with a simpler method and just open a second edit screen.

Communication is Key

When developing mobile applications for DotNetNuke it is important to spend some time planning out how your mobile application will communicate with your DotNetNuke application.  You can easily spend as much time working on the communication layer as you do on the underlying services or the mobile application. Creating a robust communication layer introduces it’s own challenges that most module developers never have to deal with.

Will your mobile application require access to secure DotNetNuke services?  Do the services you plan to use require that a user be fully authenticated or is it enough that your communication layer can provide the appropriate UserID?  If you are passing credentials, how do you ensure that your communication chanel is secure?  Is any of the data you are passing to the server or recieving from the server sensitive?

Once you have gotten the security questions out of the way, you need to determine how you want to pass data back and forth.  Do you want to use a REST based interface?  What format will you use to send and recieve data?  How will you communicate exception conditions to the mobile application?

For dnnNotes, I have elected to use Windows Communication Foundation (WCF) 4.0.  I could have chosen to use a simple ASMX based webservice, or even chosen to create my own HttpHandler.  Each choice has it’s own set of benefits and challenges.  ASMX and HttpHandler based solutions are great choices when you have a simple service layer, but as the service layer gets more and more complex, the amount of code needed for both ASMX and HttpHandlers increases rather drastically.

WCF provides a lot of flexibility in how it handles communications.  This flexibility allows us to plug-in custom behavior to handle security, error handling and even data formating, or to just use the built-in defaults.  One of the big downsides to using WCF is that it is very configuration intensive.  This will definitely complicate creating the module manifest.

The service layer will interact with the business layer for the module, but will also include the ability to designate security restrictions in the WCF service contract (Figure 3). While I could have pushed this down into the business layer, I prefer to put basic security restrictions as close to the user as possible.  We will also include security restrictions in the business layer which are common to both the UI and the service layer and serve as additional safeguards for the data.
 

Figure 3: dnnNotes Service Layer

My first step is to create a new WCF service.  In Visual Studio, add a new item to your DotNetNuke site (Figure 4). This will create a .svc file in your website root and will add a service interface (INoteService.vb) and service implementation (NoteService.vb) to your App_Code folder (I subsequently moved the code files to an external class library where I will keep all of the services and data layer for my module).

Figure 4: Creating a new WCF service

My service layer is going to expose 4 standard operations plus a simple login service which we’ll cover when we look at security.  The 4 Operations are: list, get, add and delete.  I have purposely not provided an update operation and will use the add method to handle this case.  I have chosen to use a REST like interface in that the URL will designate the operation, but I will use a POST operation to ensure that any data sent to the server can be kept secure if I choose to use SSL. Listing 3 shows our service contract up to this point.

Imports System.ServiceModel
Imports System.ServiceModel.Web

<ServiceContract()> _
Public Interface INoteService
  <OperationContract()> _
    <SecurityTokenValidator ("Registered Users")> _
    <WebInvoke (BodyStyle := WebMessageBodyStyle.Wrapped, _
                RequestFormat := WebMessageFormat.Json, _
                ResponseFormat := WebMessageFormat.Json, _
                UriTemplate := "/list")> _
  Function ListNotes (ByVal token As String) As NoteInfo()

  <OperationContract()> _
    <SecurityTokenValidator("Registered Users")> _
    <WebInvoke(BodyStyle:=WebMessageBodyStyle.Wrapped, _
                RequestFormat:=WebMessageFormat.Json, _
                ResponseFormat:=WebMessageFormat.Json, _
                UriTemplate:="/get")> _
  Function GetNote(ByVal token As String, ByVal id As Integer) As NoteInfo

  <OperationContract()> _
    <SecurityTokenValidator ("Registered Users")> _
    <WebInvoke (BodyStyle := WebMessageBodyStyle.Wrapped, _
                RequestFormat := WebMessageFormat.Json, _
                ResponseFormat := WebMessageFormat.Json, _
                UriTemplate := "/add")> _
  Sub AddNote (ByVal token As String, ByVal note As NoteInfo)

  <OperationContract()> _
    <SecurityTokenValidator("Registered Users")> _
    <WebInvoke(BodyStyle:=WebMessageBodyStyle.Wrapped, _
                RequestFormat:=WebMessageFormat.Json, _
                ResponseFormat:=WebMessageFormat.Json, _
                UriTemplate:="/delete")> _
  Sub DeleteNote(ByVal token As String, ByVal id As Integer)

  <OperationContract()> _
    <WebInvoke(BodyStyle:=WebMessageBodyStyle.Wrapped, _
                RequestFormat:=WebMessageFormat.Json, _
                ResponseFormat:=WebMessageFormat.Json, _
                UriTemplate:="/login")> _
  Function Login(ByVal username As String, _
                  ByVal password As String) As String
End Interface

Listing 3: WCF Service Contract for dnnNotes

WCF development is a huge topic that is beyond the scope of this article.  I’ll cover some of the key aspects of this particular implementation.  For more information on using WCF with DotNetNuke I recommend you read the excellent series of blog posts on GoodDogs.com by Steve Fabian where he goes into details on using WCF with DotNetNuke (http//bit.ly/GoodDogsWCF).

The key to my WCF service layer is that I will be passing all data using JSON.  This will make the service easy to consume by other web applications as well as by Titanium based applications.  During testing of my service layer I was able to use a simple jQuery based DotNetNuke module to call all of the services and examine the messages passed to and from the service. 

This highlights one of the reasons that I prefer JSON over XML or SOAP for modern web-services.  JSON is a very simple data format that is readily generated and consumed by JavaScript as well as .Net.  This means that I don’t have all the ceremony of SOAP or the heavyweight parsing requirements of XML.  My code can stay very lean on the client side.

Now that my contract is mostly complete (we’ll revisit the contract when we look at security), I can build my actual service layer as shown in Listing 4.

Imports System.ServiceModel.Activation
Imports System.Web
Imports DotNetNuke.Common.Utilities
Imports DotNetNuke.Security.Membership
Imports DotNetNuke.Entities.Users

<AspNetCompatibilityRequirements (RequirementsMode := AspNetCompatibilityRequirementsMode.Required)> _
Public Class NoteService
  Implements INoteService

  Private nc As New NotesController()
  Private request As HttpRequest = HttpContext.Current.Request

  Function GetNote(ByVal token As String, ByVal id As Integer) As NoteInfo Implements INoteService.GetNote
    Dim sectoken As SecurityToken = GetSecurityToken(token)

    Return nc.GetNote(id, sectoken.UserId)
  End Function

  Public Sub AddNote (ByVal token As String, ByVal note As NoteInfo) Implements INoteService.AddNote
    Dim sectoken As SecurityToken = GetSecurityToken (token)

    If note.ID = Null.NullInteger Then
      nc.AddNote (note, sectoken.UserId, Null.NullInteger, Null.NullInteger)
    Else
      nc.UpdateNote (note, sectoken.UserId, Null.NullInteger, Null.NullInteger)
    End If
  End Sub

  Public Function ListNotes (ByVal token As String) As NoteInfo() Implements INoteService.ListNotes
    Dim sectoken As SecurityToken = GetSecurityToken (token)

    Return nc.GetNotesByUser (sectoken.UserId).ToArray()
  End Function

  Public Sub DeleteNote(ByVal token As String, ByVal id As Integer) Implements INoteService.DeleteNote
      Dim sectoken As SecurityToken = GetSecurityToken(token)

      nc.DeleteNote(id, sectoken.UserId)
  End Sub

  Public Function Login (ByVal username As String, ByVal password As String) As String Implements INoteService.Login
    Dim loginStatus As New UserLoginStatus
    Dim user As UserInfo = UserController.ValidateUser ( _
                             0, username, password, "", "", _
                             "0.0.0.0", loginStatus)

    If user Is Nothing Then
      Throw New System.Security.SecurityException("Access Request Denied.  Invalid UserID and Password")
    End If

    Dim tokenId As Guid = Guid.NewGuid

    Dim token As New SecurityToken() With { _
          .UserId = user.UserID, _
          .UserName = user.DisplayName, _
          .PortalId = user.PortalID, _
          .IP = GetIP() _
          }

    DataCache.SetCache ("DNNSecurityToken_" & tokenId.ToString(), _
                        token, New TimeSpan (0, 5, 0))

    Return tokenId.ToString()

  End Function

  Private Function GetSecurityToken (ByVal tokenid As String) As SecurityToken
    Return DataCache.GetCache ("DNNSecurityToken_" & tokenid)
  End Function
End Class

Listing 4: dnnNotes Service Layer

Notice that each of my service methods are generally just a small wrapper that uses the security token to identify the user and then delegates the real work to the underlying business controller.  The AddNote method is the only service method, besides login, that has any complexity and that is only because I didn’t want to add a dedicated update method.

Implementing Security

The most complex part of our service is the login method.  As you will notice, each of my other service methods uses a “token” parameter as the first parameter.  Because dnnNotes provides each user their own separate note store, we have to be able to identify the current user for every operation. 

To identify users, I have provided the login function which allows a client application to pass the username and password to our service.  I would generally put this method behind an SSL endpoint so that the credentials are encrypted.  Because we defined the service contract for this method using the WebInvoke attribute (see listing 3), our data will be sent to the server in the body of a POST request.  This ensures that the data can be encrypted.  Had we instead decided to use a WebGet attribute in our contract, we would have ended up sending our password as part of the URL which is insecure, regardless of whether we use SSL or not.

The login function stores some user details in the cache that we can use later to apply additional security checks.  These details are stored using a guid as the cache key.  This token is returned to the client application which then will use this token for every subsequent service request.

Notice that my service doesn’t do any validation of this token in the previous code.  In order to centralize my security code and push it out in front of my service layer I will take advantage of the extensibility of WCF.

One of the primary methods of extending WCF is with the use of custom behaviors.  Behaviors allow you to change the way WCF works by injecting interceptors at different stages in the WCF pipeline.  Depending on the granularity that is needed, you can apply Service Behaviors, Endpoint Behaviors, Contract Behaviors and Operation Behaviors.

For this application I want to be able to apply security on each method. Clearly the login method should be open to any client, but the other methods will need to be secured.  To enable this scenario I’ll create a custom SecurityTokenValidator attribute which is applied to each method of the service contract.  The custom attribute implements the IOperationBehavior interface in order to add a parameter inspector as shown in Listing 5.

Imports System.ServiceModel.Description
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Dispatcher

<AttributeUsage(AttributeTargets.All)> _
Public Class SecurityTokenValidator
  Inherits Attribute
  Implements IOperationBehavior

  Public Roles As String

  Public Sub New(ByVal value As String)
    Roles = value
  End Sub

  …

  Public Sub ApplyDispatchBehavior( _
             ByVal operationDescription As OperationDescription, _
             ByVal dispatchOperation As DispatchOperation) _
    Implements IOperationBehavior.ApplyDispatchBehavior

    dispatchOperation.ParameterInspectors.Add( _
        New SecurityTokenInspector(Roles))

    Return
  End Sub

  …

End Class

Listing 5: Defining a custom Security Behavior for WCF

In practice I will just add a SecurityTokenValidator attribute to the service contract for each of my methods and define the role that is required to access that method.  In Listing 6 I have added the validator to the ListNotes method and require the user to be a member of the “dnnNotes Users” role.  I’ll create this role as part of the module installation.  This behavior will allow admins to limit users who can use the dnnNotes service.

  <OperationContract()> _
    <SecurityTokenValidator("dnnNotes Users")> _
    <WebInvoke(BodyStyle:=WebMessageBodyStyle.Wrapped, _
                RequestFormat:=WebMessageFormat.Json, _
                ResponseFormat:=WebMessageFormat.Json, _
                UriTemplate:="/list")> _
  Function ListNotes(ByVal token As String) As NoteInfo()

Listing 6: Applying Security to dnnNotes

The SecurityTokenInspector is where the real security work happens.  A parameter inspector allows you to look at the operation parameters before the call and after the call and make any needed changes to the parameters or to perform validations.  As shown in Listing 7, each of my secured methods will need to include the token returned from the login method as the first parameter in the method call.  It is important to distinguish between the order in which the parameter is defined in the contract from the order in which the parameters are defined in the JSON data passed to the service.  The JSON data element order is unimportant, it is only the contract parameter order which matters.

Imports System.ServiceModel.Dispatcher
Imports DotNetNuke.Common.Utilities
Imports DotNetNuke.Entities.Users

Public Class SecurityTokenInspector
  Implements IParameterInspector

  Public Roles As String

  Public Sub New (ByVal value As String)
    Roles = value
  End Sub

  Public Function BeforeCall (ByVal operationName As String, _
                              ByVal inputs() As Object) As Object _
    Implements IParameterInspector.BeforeCall

    ' token will always be the first parameter
    Dim TokenId As String = inputs (0).ToString
   
    ' first make sure token exists
    Dim token As SecurityToken = DataCache.GetCache( _
                                  "DNNSecurityToken_" & TokenId)

    If token Is Nothing Then
      Throw New Exception.InvalidTokenException( _
                "Security Token Expired. Please request a new Token")
    End If

    If token.IP <> GetIP() Then
      Throw New Exception.InvalidTokenException( _
                "Invalid token. Please request a new token")
    End If

    ' if token exists, check user roles
    Dim user As UserInfo = UserController.GetUserById( _
                             token.PortalId, _
                             token.UserId)
    If Not user.IsInRole (Roles) Then
      Throw New System.Security.SecurityException( _
                "Access Denied. Role Membership Requirements not met")
      Return Nothing
    End If

    Return Nothing
  End Function

  Public Sub AfterCall (ByVal operationName As String, _
                        ByVal outputs() As Object, _
                        ByVal returnValue As Object, _
                        ByVal correlationState As Object) _
    Implements IParameterInspector.AfterCall
    Return
  End Sub
End Class

Listing 7: IparameterInspector Implementation for Security

Once I have identified the parameter which represents the token, then I can use that token to get a SecurityToken object from the cache, and to subsequently use the SecurityToken to lookup the user object.  In an effort to make it harder for someone to “steal” the token and use it to impersonate the user, I also compare the IP address of the request with the IP address that is stored in the SecurityToken.  This is a simplistic check but it highlights that you have complete control over how you want to validate that the token guid has not been compromised. Once I have the user, I can then perform a simple DotNetNuke call to verify that the user is a member of the specified role.

Handling Exceptions

Throughout the SecurityTokenInspector I throw exceptions to deal with failed security checks.  I also throw exceptions in the service layer and have to anticipate that exceptions may be thrown as well by DotNetNuke during the service call.  Unfortunately, WCF does not automatically return these exceptions using JSON and instead you get a normal error page. 

This default behavior does not conform to the expectations of the service clients which expect to send and recieve all data as JSON.  Once again, I’ll use the extensibility of WCF to define a new behavior.  Because this behavior should apply to all of my service operations for this particular endpoint, I will define a new EndpointBehavior that inherits from WebHttpBehavior called JsonWebHttpBehavior as shown in Listing 8.  Like the OperationBehavior that setup the parameter inspector for security, this new behavior is only used for setting up a generic error handler.

Imports System.ServiceModel.Description
Imports System.ServiceModel.Dispatcher

Public Class JsonWebHttpBehavior
  Inherits WebHttpBehavior

  Protected Overrides Sub AddServerErrorHandlers( _
                          ByVal endpoint As ServiceEndpoint, _
                          ByVal endpointDispatcher As EndpointDispatcher)

    endpointDispatcher. _
      DispatchRuntime. _
      ChannelDispatcher. _
      ErrorHandlers.Add( _
      New JsonErrorHandler(endpointDispatcher. _
                              DispatchRuntime. _
                              ChannelDispatcher. _
                              IncludeExceptionDetailInFaults))
  End Sub
End Class

Listing 8: New Behavior for Custom Error Handler

The JsonErrorHandler shown in Listing 9, deals with converting any exception that happens within the context of the service call, into a JSON object using the DataContractJsonSerializer.  The error handler uses the fault Message that is passed by reference into the ProvideFault method to return a message with the appropriate content and headers so that WCF will return a JSON formatted exception to the client application. 

Imports System.ServiceModel.Dispatcher
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Web
Imports System.Net
Imports System.Runtime.Serialization.Json

Public Class JsonErrorHandler
  Implements IErrorHandler
  Public Sub New (ByVal includeExceptionDetailInFaults As Boolean)
    Me.includeExceptionDetailInFaults = includeExceptionDetailInFaults
  End Sub

  Public Function HandleError(ByVal err As System.Exception) As Boolean Implements IErrorHandler.HandleError
    Return False
  End Function

  Public Sub ProvideFault(ByVal err As System.Exception, _
                           ByVal version As MessageVersion, _
                           ByRef fault As Message) Implements IErrorHandler.ProvideFault

    fault = GetJsonFaultMessage(err, version)
    ApplyJsonSettings(fault)

    WebOperationContext.Current.OutgoingResponse.ContentType = "application/json"
    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK
  End Sub

  Private Function GetJsonFaultMessage(ByVal err As System.Exception, ByVal version As MessageVersion) As Message
    Dim jsonFault As JsonFault
    If includeExceptionDetailInFaults Then
      jsonFault = New JsonFault() With { _
        .ExceptionType = err.GetType().FullName, _
        .Message = err.Message, _
        .StackTrace = err.StackTrace _
        }
    Else
      jsonFault = New JsonFault() With { _
        .ExceptionType = GetType(System.Exception).FullName, _
        .Message = "An error occurred on the server. See server logs for details.", _
        .StackTrace = Nothing _
        }
    End If

    Return Message.CreateMessage(version, Nothing, jsonFault, New DataContractJsonSerializer(GetType(JsonFault)))
  End Function

  Private Sub ApplyJsonSettings (ByRef fault As Message)
    Dim formatting As New WebBodyFormatMessageProperty (WebContentFormat.Json)
    fault.Properties.Add (WebBodyFormatMessageProperty.Name, formatting)
  End Sub

  Private includeExceptionDetailInFaults As Boolean
End Class

Listing 9: Implementation for the Custom Error Handler

Adding our new error handler is a simple matter of making the necessary configuration entries in web.config.  At this point we are complete with the coding portion of the services layer.  The only step that remains is making the appropriate changes to web.config.  We’ll hand enter this configuration information during development, but will include it as part of the module installer so that others installing the module should not require any knowledge of WCF in order to use the module.

The WCF configuration section that we’ll need for our module is outlined in Listing 10.  It is basically comprised of three sections: services, behaviors and extensions.  The services section is where we define the endpoints for our service.  In the behaviors node we’ll define the behaviors associated with our service and with our endpoint.  Finally, the extensions section is where we’ll hook up the JsonWebHttpElement for our error handling.

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  <behaviors>
    <serviceBehaviors>
      <behavior name="NoteServiceBehavior">
        <serviceMetadata httpGetEnabled="true"/>
        <serviceDebug includeExceptionDetailInFaults="true"/>
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="httpBehavior">
        <webHttp />
        <jsonWebHttp />
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
      <add name="jsonWebHttp" type="DotNetNuke.Modules.dnnNotes.JsonWebHttpElement, dnnNotes"/>
    </behaviorExtensions>
  </extensions>
  <services>
    <service behaviorConfiguration="NoteServiceBehavior" name="DotNetNuke.Modules.dnnNotes.NoteService">
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      <endpoint binding="webHttpBinding" contract="DotNetNuke.Modules.dnnNotes.INoteService" behaviorConfiguration="httpBehavior"/>
    </service>
  </services>
</system.serviceModel>

Listing 10: Configuring WCF for dnnNotes


I know that there was a lot of handwaving in this section and it is ok if you don’t understand everything.  As I said at the outset, WCF is a very deep topic and is not something that you will understand from a single article.  Hopefully you have enough information to realize how flexible and powerful WCF can be, and that with a little bit of research you can build a pretty robust service layer that is tailored for your specific needs.
dnnNotes for Android
Now that we have the module and services layer created we can turn our attention to the final portion of our mobile application.  As I discussed at the outset of this article, I’ll use Appcelerator Titanium to handle the development of the mobile application.  Our mobile application is only going to speak to our services layer as shown in Figure 5.


Figure 5: dnnNotes Mobile Application Architecture

Compared to the rest of the application, the mobile side will be pretty straight forward since it is really just a thin UI layer over the top of the services layer.  Often, when developing mobile applications with a server side component, you will end up placing most of your business rules on the server where you have the most control. 

Building the Android Mobile Client
Whenever building any application, it is important to understand the general flow of the application.  What screens do you need to create, how will you navigate between screens, where will you need to interact with the data or service layer and whether you’ll need to store any data locally on the device.

For my sample application, I’ll keep things fairly simple since I am really just concerned with making sure you understand the basics of building apps using Titanium and that you have a good appreciation for the capabilities of the platform.  After launching Titanium I will go ahead and create a new project as shown in figure 6.  Notice that the iPhone SDK is not installed which means that I will not be able to generate the iPhone version of the application.  The iPhone SDK is only available for MacOS, so if you want to code for the iPhone, you will need access to a MacOS computer.

Figure 6: Creating a new Titanium Project

Titanium will create a skeleton application that contains a build folder, a resources folder and some miscelaneous files for changelogs, licenses and readme files.  In addition there will be a tiapp.xml which contains our basic application configuration.  You can pretty much leave the build folder and the miscellaneous files alone as you shouldn’t need to make any changes to them in order to build an application.

NOTE: There are some cases where you might want to edit the tiapp.xml or change some settings in the build folder in order to customize behaviors which are not configurable from code.  You can find more information about what is contained in these files and folders in the developer section of the Appcelerator site at http://developer.appcelerator.com/

The resources folder contains an app.js file which is actual source code for the default sample application.  This folder also includes a couple of images which you can delete, since you will probably want your own.  Also, Titanium will include and Android and iPhone directory depending on which SDKs you have installed.  These folders are where you’ll place platform specific media elements.  The iPhone and Android platforms have a few differences in screen size and icon sizing so you’ll need to plan for that when creating your own graphic elements. 

Mobile applications are generally very graphically intense.  As I said before, it is basically just a UI layer.  iPhone and Android users have come to expect rich experiences, and if you want to create mobile applications which are well regarded by your users, then you will want to spend a little bit of time on the graphics.  Fortunately, Titanium began life as an HTML, CSS and JavaScript based solution.  Althought the HTML and CSS are no longer at the core of the Titanium experience, Titanium still understands the importance of graphic elements in mobile applications and makes it easy for you to access and use graphics in your application.

For my dnnNotes application, I will use the “notes” theme throughout the application to give the application a playful feel to it.  Titanium included a default splash screen in the android folder which I have replaced with the image in Figure 7.  I also have a number of graphic elements which I’ll use to build up my main navigation screen as shown in figure 8.

Figure 7: dnnNotes Splash Screen

Figure 8: dnnNotes Navigation

From my home screen, the user will be able to enter their user credentials which the application will use for logging in.  The user will also be able to create notes and view their existing notes.

All Titanium applications start from an app.js (remember that this was the only code file included in the sample application), which is where we will begin our code development for dnnNotes.  I am using Eclipse for editing my code, but you are free to use your JavaScript editor of choice.  Opening app.js I have deleted all the existing code and will start with just a few basic screen elements (Listing 11).

Titanium.UI.setBackgroundColor('#fff');

var viewContainer = Titanium.UI.createView({
  top:80,
  width:320,
  height:400
});

var app = Titanium.UI.createWindow({
  backgroundImage:'images/bg.png'
});
app.add(viewContainer);
app.open({
 transition:Titanium.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
});

Listing 11: Implementation for the Custom Error Handler

This code is pretty basic but it already shows us a couple of key programming elements for working with Titanium.  The first thing that you should notice is that Titanium has an API that hangs off of the Titanium namespace (you can also shorten this to just Ti).  Pretty much any API method or object that you deal with will be in this namespace.

The second item I want to point out here is the createView and createWindow method calls.  Windows and Views are the cornerstone of Titanium applications.  Windows are generally a heavyweight construct and will define a separate context with its own set of variables.  Views however are a lightweight construct which much be added to a Window in order to be visible.  Every application must have at least one window, but may may zero or more views.  In practice, most Titanium applications will have many different views.  In many respects, views are like divs in HTML – they are a lightweight container that is used to aid in laying out the user interface.

The third item to take away from this code snippet is that whenever we create an object in Titanium, we will generally want to set properties on that object.  Usually, we are setting properties to position or style the element or to control the behavior of the object.  In my experience, a large percentage of the code I write in Titanium is just setting up properties on objects.  Currently there is no “form” editor or designer for Titanium so I have to layout everything by hand.

The final step in app.js is to open the window that I had created.  As I previously pointed out, every application must have at least one window.  As you can see creating a basic mobile application is really easy, even if it is in JavaScript.

If I run the application at this point, it is a very dissapointing experience.  I have the splash screen as shown in Figure 7 which goes away and then shows me a blank notebook page as shown in Figure 9.  I can now start adding some additional code to start building up my main navigation page.

Figure 9: dnnNotes Background

My next step is to add some code to create a header section of the page.  This area will contain a few common elements that I want to be visible throughout the life of the application.  I will swap out elements in the main “body” section, but the header will remain constant.  In order to keep my code nice and tidy, I’ll place the code for the header in a separate js file in the application root. It is ok to use folders to organize your code, but unless you have a really large codebase, I tend not to do that.

My header code (shown in listing 12) is also not very complicated.  It creates a view which acts as the container for the header.  It then creates a button and adds it to the container and then creates a label and adds that to the container as well.  Finally, I add an eventlistener to the button.

var viewHeader = Titanium.UI.createView({
  top:0,
  left:0,
  height:80,
  width:320
});

var profile = Titanium.UI.createButton({
 backgroundImage:'images/profile-button.png',
 width:70,
 height:36,
 right:10,
 top:10
});
viewHeader.add(profile);

var username = Titanium.UI.createLabel({
 color:'#92c0c3',
 text:'',
 textAlign:'right',
 height:'auto',
 font:{fontSize:16,fontWeight:'bold'},
 right:10,
 bottom:10
});
viewHeader.add(username);

profile.addEventListener("click",function(e) {
  Titanium.App.fireEvent("profileClicked");
});

Listing 12: Creating a simple Header in Titanium

The event listener code is an important item to highlight here.  You will often want to communicate between different views and windows in your application.  Titanium allows you to define custom events using the Titanium.App.fireEvent method.  If another part of your code adds an event listener on the Titanium.App object for the “profileClicked” event, then that code will get fired whenever the profile button is clicked.

At this point I have the app.js file and the header.js file but the header.js file is not used anywhere.  To fix this, I’ll modify my app.js file and add the following line just below my call to setBackgroundColor.

Titanium.include('header.js');

This line will basically inject the header.js file into the app.js file, similar to the way that server side includes work in ASP and ASP.Net.  The JavaScript is executed imediately, just as if I had included everything within the app.js file itself.  I rely on this capability a lot in Titanium since it allows me to segment my code and re-use portions of it withing different windows of my application.

I also have to remember that the header created a view, but that view has not been added to a window.  We do that also in app.js and add the following line just above the app.add(viewContainer) line.

app.add(viewHeader);

In order to keep dnnNotes pretty simple, we are just going to use a single master window and swap out views to show different sections of the application.  In listing 11 I created the viewContainer object which I essentially left empty.  I’ll add all of my application views to this container, and then show or hide them as appropriate.

The first view I want to create is the view for editing my user credentials and logging into dnnNotes.  Whenever we press the “Login” button in the header I want to show the view.  If I press the Login button a second time I’ll hide the Login screen and return to my main menu.  I’ll create this view in the config.js file and will call this view “config”.  All elements in the config.js file will be contained in the config view so that by showing or hiding this view, I can show or hide the entire UI for entering and saving credentials (Listing 13).

var config = Titanium.UI.createView( {
  width : 320,
  height : 420,
  visible : false
});

var configLabel = Ti.UI.createImageView( {
  url : 'images/config.png',
  top : 20
});

config.add(configLabel);

var usernameVal = Titanium.App.Properties.getString("un");
var passwordVal = Titanium.App.Properties.getString("pw");
var siteURL = Titanium.App.Properties.getString("surl")

var unField = Titanium.UI.createTextField( {
  color : '#787878',
  value : usernameVal,
  height : 35,
  top : 95,
  width : 250,
  hintText : 'dnnNotes Username',
  keyboardType : Titanium.UI.KEYBOARD_DEFAULT,
  returnKeyType : Titanium.UI.RETURNKEY_DEFAULT,
  borderStyle : Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
  autocorrect : false
});
unField.addEventListener('return', function() {
  unField.blur();
});
unField.addEventListener('change', function(e) {
  usernameVal = e.value;
});

config.add(unField);

var pwField = Titanium.UI.createTextField( {
  color : '#787878',
  value : passwordVal,
  height : 35,
  top : 135,
  width : 250,
  hintText : 'dnnNotes Password',
  keyboardType : Titanium.UI.KEYBOARD_DEFAULT,
  returnKeyType : Titanium.UI.RETURNKEY_DEFAULT,
  borderStyle : Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
  autocorrect : false,
  passwordMask : true
});
pwField.addEventListener('return', function() {
  pwField.blur();
});
pwField.addEventListener('change', function(e) {
  passwordVal = e.value;
});

config.add(pwField);

var surlField = Titanium.UI.createTextField( {
  color : '#787878',
  value : siteURL,
  height : 35,
  top : 175,
  width : 250,
  hintText : 'dnnNotes Site URL',
  keyboardType : Titanium.UI.KEYBOARD_DEFAULT,
  returnKeyType : Titanium.UI.RETURNKEY_DEFAULT,
  borderStyle : Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
  autocorrect : false
});
surlField.addEventListener('return', function() {
  surlField.blur();
});
surlField.addEventListener('change', function(e) {
  siteURL = e.value;
});

config.add(surlField);

var saveButton = Titanium.UI.createButton( {
  top : 215,
  backgroundImage : 'images/save-button.png',
  width : 165,
  height : 53
});
config.add(saveButton);

// This is the heart of the Login function showing use of the
// dnnNotes service
saveButton.addEventListener("click", function(e) {
  Titanium.App.Properties.setString("un", usernameVal);
  Titanium.App.Properties.setString("pw", passwordVal);
  Titanium.App.Properties.setString("surl", siteURL);

  actInd.show();
  var xhr = Titanium.Network.createHTTPClient();

  xhr.onerror = function(e) {
    actInd.hide();
    var alrt = Titanium.UI.createAlertDialog( {
      title : 'I\'m not sure how to say this...',
      message : 'The server doesn\'t want to talk to you right now.'
    });
    alrt.show();
    showMenu();
  };

  xhr.onload = function() {
    actInd.hide();
    var response = JSON.parse(this.responseText);

    Ti.API.info('notes=' + this.responseText);
    if (response.ExceptionType) {
      Titanium.App.Properties.removeProperty("token");
      var alrt = Titanium.UI.createAlertDialog( {
        title : 'Are you sure?',
        message : 'The server doesn\'t like those credentials..'
      });
      alrt.show();
    } else {
      Titanium.App.Properties.setString("token", response.LoginResult);
      username.text = usernameVal;
      showMenu();
    }
    ;
  };

  var loginUrl = siteURL + svcbase + loginsvc;

  xhr.open('POST', loginUrl);
  xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
  xhr.setRequestHeader('Accept', 'application/json');

  var data = {
    'username' : usernameVal,
    'password' : passwordVal
  };
  xhr.send(JSON.stringify(data));

});

config.addEventListener("click", function(e) {
  unField.blur();
  pwField.blur();
  surlField.blur();
});

Listing 13: Creating the Login screen

As you can see, most of this code should be familiar by now as it is just adding some buttons, images and textviews to the page.  One of the items that is new is the use of the Titanium.App.Properties.getString and setString methods.  These methods will allow us to store small bits of information as strings to the local device.  This is very handy for caching data on the client and is perfect for storing application settings.  As your needs grow, it is also possible to use SQLite in Titanium, but that is definitely overkill for my application.

Also, this method is a good example of how I call the dnnNotes service layer using the XmlHttpRequest object.  Using XHR generally follows the same pattern:
1. Create the XHR object
2. Define error and onload callback functions
3. Open the XHR object
4. Set any XHR headers
5. Send a request, providing data as required

Once the request has been sent, the onload and onerror callback functions will handle an response from the server and take the appropriate action.  The current version of the platform has a bug which impacts how you need to handle errors.  Normally, you should have access to any data returned in the body of the response as part of the XHR object in the onerror function.  The current version of the platform short-circuits the request such that the data is not available.  To overcome this issue, I ensure that the service layer exception handler returns a 200 status code which the client will not view as an error condition.  I then rely on the body of the message to determine if an error occured and to get the actual error type.

Again I’ll make the needed addition to app.js to import this file and to add the view to the viewContainer.  I’ll also use this opportunity to start adding the methods that will manage our views.  I’ll add one method in app.js for each of the main views that will be in my application.  I’ll have a view for configuration, a view for the main menu, a view to list my notes and a view to edit or re-view a note.  These methods all follow the same pattern shown in Listing 14.  By calling the showConfig method from anywhere within my application I can hide all of the other application views and show my configuration view.

function showConfig() {
  viewContainer.animate( {
    view : config,
    transition : Ti.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
  });
  menu.visible = false;
  config.visible = true;
  noteslist.visible = false;
  noteview.visible = false;
}

Listing 14: Creating the Login screen

When I’m going to show the note screen or the list of notes, I’ll need to validate that the user is logged in first, and I’ll want to fire a custom event so that the screen knows that it is time to reload data from the server.  Validating the user is logged on is just a matter of calling Ti.App.Properties.hasProperty(“token”), since this value only exists if the user has successfully logged in.  If the token subsequently expires, I’ll catch that in the service call to get the additional data.

Often when working with mobile apps, you will need to display lists or tables of data.  These lists are often just summaries of some underlying set of data which allows the user to select a row to drill down and see more details.  Whether we are talking about lists of venues to check into on foursquare, a list of tweets on twitter or a list of notes in dnnNotes, I’ll want to provide an easy method for my users to see their notes and drill down to manage a single note.

The Titanium API includes a TableView object which is used just for this purpose.  In creating the page for viewing my notelist I’ll take advantage of some of the basic TableView functionality to provide a nice navigation element for my users.  To use the TableView requires 4 basic steps (not necessarily executed in order): 1) create the tableview, 2) add some tableviewrows, 3) Setup an eventlistener to handle clicking on a row, and 4) add the tableview to an existing window or view.

Listing 15 shows these 4 steps for my noteslist.js page (I am only showing the relevant sections of code).  As you can see, working with tableviews is pretty straight forward.

// We could provide data in the constructor
// if it was available.  In this case I’ll load it
// later.
var tableOfNotes = Titanium.UI.createTableView({
  top: 50,
  left: 60,
  right: 30,
  bottom: 120,
  scrollable: true,
  separatorStyle: 0,
  separatorColor:'transparent'
});

// Make sure the tableview is visible
noteslist.add(tableOfNotes);

// Handle the click event for rows
tableOfNotes.addEventListener('click', function(e)
{
  // event data
  var index = e.index;
  var section = e.section;
  var row = e.row;
  var rowdata = e.rowData;
  Titanium.UI.createAlertDialog({title:'Table View',message:'row ' + row + ' index ' + index + ' section ' + section  + ' row data ' + rowdata}).show();
});

// Standard web service call
Titanium.App.addEventListener("loadNotesList",function(e) {
  actInd.show();
  var xhr = Titanium.Network.createHTTPClient();

  xhr.onload = function() {
    actInd.hide();
    var response = JSON.parse(this.responseText);

    if (response.ExceptionType) {
      //handle exception
    } else {
      //clear existing data
      tableOfNotes.setData([]);
     
      //load the data into the TableView
      var notes = response.ListNotesResult;
      if (notes){
        for ( var i = 0; i < notes.length; i++) {
          tableOfNotes.appendRow({
            color: '#222',
            height: 30,           
            hasChild:true,
            title:notes[i].Title,
            noteid:notes[i].ID
          });
        }
      }
    };
  };

  var listUrl = siteURL + svcbase + listsvc;
 
  xhr.open('POST', listUrl);
  xhr.setRequestHeader("Content-Type",
      "application/json; charset=utf-8");
  xhr.setRequestHeader('Accept', 'application/json');

  var data = {
    'token' : Titanium.App.Properties.getString("token")
  };
 
  xhr.send(JSON.stringify(data));
});

Listing 15: Showing lists of data with the TableView

At this point we have most of the pieces for a fully functional mobile application.  It is able to navigate to multiple views or windows, display data, process user input and communicate with our server.  All of these are fundamental building blocks for just about any mobile application.  As you can see in the code examples, much of the code that I have written is concerned with creating and displaying UI elements.  If you could strip all of that away, there would not be a lot of code remaining.

The last step in using Titanium to create mobile applications is to actually compile and test our application.  During the course of development, I will compile my applications hundreds, if not thousands of times.  Because Titanium does not include a layout designer, the only way to see the layout of each screen is to run the code.  This should improve in 2011 when an updated Titanium development environment is introduced.

To execute your application in the emulator, open Titanium Developer and select your project from the project list.  Select the “Test & Package” button as shown in Figure 10.  By default the Run Emulator tab is shown and you will be able to launch the emulator from there.

Figure 10: Launching the Emulator

One of the other drawbacks to the Titanium development experience is that it does not include any method for stepping through your code and debugging it (this should be addressed with the new development environment in 2011).  Instead, what you are left with is sprinkling your code with Ti.API.info method calls.  These are essentially debug.writeln statements for Titanium.  While not nearly as effective as being able to step through code, I found that it is still a very workable solution.

Summary 

Building mobile applications does not have to be difficult.  Like any application development effort, it is really just a matter of breaking it down into a series of managable pieces.  When developing mobile applications with a server side component it is especially important to spend a bit of time thinking through the service layer as this can make writing the mobile client extremely simple, or extremely complex.  The more you can isolate business rules behind the service layer, the easier it will be to write the client application.

One aspect of mobile development that we did not cover here, but that I recommend you explore on your own is how to use the native capabilities of a particular device using the Titanium API.  Titanium gives pretty broad access to the most common hardware and software features like mapping, GPS, camera, contacts etc.  Integrating these elements into your mobile application is pretty straight forward and will allow you to develop cutting edge mobile applications in a fraction of the time it would normally take using more traditional mobile development tools.
 

Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar