Written by: 2/13/2011 4:45 PM
In this section of the “DotNetNuke and WCF” series, I will show how you can use everything you’ve learned to expose some Module “functionality”. So far, we’ve used WCF to expose some Data about our web site. But hopefully you can see that we are not limited to just exposing data, but that we can also expose module functionality using the same methods.
I’m going to use the Repository module as an example. This approach can be used with any module, but for this example, the Repository module is a good choice as it does have some ‘administrative’ functions that we provide offline.
You can configure the Repository module to allow user uploads, and optionally require user uploads to be moderated. In this WCF example, we will add an additional OperationContract to expose the module’s data, and write a desktop Forms application that will monitor our web site and alert us when a user uploads a new file to our module. We will also add an OperationContract that will allow us to to approve that upload right from our desktop application without ever having to visit our web site.
This is just a simple example, but hopefully you’ll think about the modules you are using on your sites and this will spark some ideas on how to use WCF to extend your content administration to your desktop, providing the ability to monitor your site and even administer your content from a desktop or mobile application.
So, let’s get started
Since we are hosting our WCF service within our DotNetNuke web site, we have full access to not only the DotNetNuke namespace and assembly, but any modules that are installed as well.
Here’s the plan for our service… we’ll first check to see if the Repository module is installed on the site, then if it is, we’ll check all of the Repository modules on our site to see if there are any uploads that require moderation, and finally, we’ll provide a method to approve the upload.
We’ll need two OperationContracts, GetUnapprovedRepositoryFiles() and ApproveRepositoryFile()
We’ll also need to create a DataContract, so that our service can provide complex data objects (Repository data items), as well as control what information is sent from the service back to the client (provide the minimum data that the client will need to do the job).
Creating the OperationContacts:
Open the IDNNService.vb interface file and add the following…
First, one of our methods will return a List of items, so let’s take advantage of Generic Lists and import the Generics namespace
1: Imports System.Collections.Generic
Then, we add our two new OperationContracts()
1: <OperationContract()> _ 2: <WebGet(ResponseFormat:=WebMessageFormat.Xml, _ 3: UriTemplate:="metrics")> _ 4: Function GetMetrics() As Metrics 5: 6: <OperationContract()> _ 7: <WebGet(ResponseFormat:=WebMessageFormat.Xml, _ 8: UriTemplate:="repository/get/{portalid}")> _ 9: Function GetUnapprovedRepositoryFiles(ByVal portalid As String) _ 10: As List(Of RepositoryFile) 11: 12: <OperationContract()> _ 13: <WebGet(ResponseFormat:=WebMessageFormat.Xml, _ 14: UriTemplate:="repository/approve/{itemid}")> _ 15: Function ApproveRepositoryFile(ByVal itemid As String) As Boolean
1: <OperationContract()> _
2: <WebGet(ResponseFormat:=WebMessageFormat.Xml, _
3: UriTemplate:="metrics")> _
4: Function GetMetrics() As Metrics
5:
6: <OperationContract()> _
7: <WebGet(ResponseFormat:=WebMessageFormat.Xml, _
8: UriTemplate:="repository/get/{portalid}")> _
9: Function GetUnapprovedRepositoryFiles(ByVal portalid As String) _
10: As List(Of RepositoryFile)
11:
12: <OperationContract()> _
13: <WebGet(ResponseFormat:=WebMessageFormat.Xml, _
14: UriTemplate:="repository/approve/{itemid}")> _
15: Function ApproveRepositoryFile(ByVal itemid As String) As Boolean
Take a look at the UriTemplates above. It is convenient to adopt a naming convention for your URIs. I usually start my URIs with the resource I am targeting, in this case the Repository module, then I follow with the verb ( what actionI want to perform ), then a resource identifier to pinpoint which resource item, or items, I want to perform that action on, and finally if there is any further clarification of the items I want to work upon.
So, for example, /repository/get/{portalid}/pending, indicates that I am addressing a repository resource, I am performing a 'get' on that resource, I am constraining the collection to a specific portal id, and finally only those items with a status of pending.
In the second example, /repository/approve/{itemid}, I am again addressing a repository resource, I will be performing an 'approve' on that resource and finally passing the unique 'itemid' that will identify which repository resource I want to approve. If you get in the habit of constructing your URIs in that manner, your API and RESTful interface will feel very comfortable to work with.
Creating the DataContract:
Next, we need to define our DataContract(). We take a look at the Repository data schema and figure out which data elements we need to return. Having done that, we define our Public Class RepositoryItems and attribute it to mark it as a DataContract. We don’t need to return the entire Repository schema, we can decide which fields are going to be necessary for an offline administrator to review and approve the upload. So, in this case, we need the ItemID, the Title, Description and the User who uploaded it. Of course, if you decide you need more data, just add more [DataMember]s to your DataContract.
1: <DataContract()> _ 2: Public Class RepositoryFile 3: 4: <DataMember()> _ 5: Public ItemID As Integer 6: <DataMember()> _ 7: Public Title As String 8: <DataMember()> _ 9: Public Description As String 10: <DataMember()> _ 11: Public User As String 12: 13: End Class
1: <DataContract()> _
2: Public Class RepositoryFile
3:
4: <DataMember()> _
5: Public ItemID As Integer
6: <DataMember()> _
7: Public Title As String
8: <DataMember()> _
9: Public Description As String
10: <DataMember()> _
11: Public User As String
12:
13: End Class
Now that we’ve defined our interface, we move on to write the implentation. Open the DNNService.vb file. We’ll have to write three functions to implement the two new OperationContracts we just defined, one to return the list of unapproved items, one to approve an item and a private method to find all the Repository modules on my site.
The Implementation:
The first OperationContract we need to implement is the GetUnapprovedRepositoryFiles(portalid). This function will find all of the Repository modules in the portal specified by the portalid parameter (using our private function), then return each item that requires moderation in a List of RepositoryFile objects . Since we’re running this WCF service within the DotNetNuke context, we can simply add a reference to DotNetNuke.Modules.Repository.dll.
1: Public Function GetUnapprovedRepositoryFiles(ByVal portalid _ As String) _ 2: As List(Of RepositoryFile) _ 3: Implements IDNNService.GetUnapprovedRepositoryFiles 4: 5: Dim results As New List(Of RepositoryFile) 6: Dim pID As Integer = Int32.Parse(portalid) 7: 8: Dim repController As New Repository.RepositoryController 9: Dim modules As List(Of ModuleInfo) = GetRepositoryModules(pID) 10: 11: For Each item As ModuleInfo In modules 12: 13: Dim items As ArrayList = _ repController.GetRepositoryObjects( _ 14: item.ModuleID, _ 15: "", "CreatedDate", 0, -1, "", -1) 16: 17: For Each repositoryItem As Repository.RepositoryInfo In _ items 18: 19: Dim newItem As New RepositoryFile() With { _ 20: .ItemID = repositoryItem.ItemId, _ 21: .Title = repositoryItem.Name, _ 22: .Description = repositoryItem.Description _ 23: } 24: 25: Dim user As UserInfo = UserController.GetUserById(pID, _ 26: repositoryItem.CreatedByUser) 27: 28: newItem.User = user.Username 29: 30: results.Add(newItem) 31: 32: Next 33: 34: Next 35: 36: Return results 37: 38: End Function
1: Public Function GetUnapprovedRepositoryFiles(ByVal portalid _
As String) _
2: As List(Of RepositoryFile) _
3: Implements IDNNService.GetUnapprovedRepositoryFiles
4:
5: Dim results As New List(Of RepositoryFile)
6: Dim pID As Integer = Int32.Parse(portalid)
7:
8: Dim repController As New Repository.RepositoryController
9: Dim modules As List(Of ModuleInfo) = GetRepositoryModules(pID)
10:
11: For Each item As ModuleInfo In modules
13: Dim items As ArrayList = _
repController.GetRepositoryObjects( _
14: item.ModuleID, _
15: "", "CreatedDate", 0, -1, "", -1)
16:
17: For Each repositoryItem As Repository.RepositoryInfo In _
items
18:
19: Dim newItem As New RepositoryFile() With { _
20: .ItemID = repositoryItem.ItemId, _
21: .Title = repositoryItem.Name, _
22: .Description = repositoryItem.Description _
23: }
24:
25: Dim user As UserInfo = UserController.GetUserById(pID, _
26: repositoryItem.CreatedByUser)
27:
28: newItem.User = user.Username
29:
30: results.Add(newItem)
31:
32: Next
33:
34: Next
35:
36: Return results
37:
38: End Function
As we get each item, we create a new RepositoryFile (line 19) object, copy the data into our new object (lines 20-22), then use the DotNetNuke UserController to get the username of the user who uploaded the item (line 25). Then we add our new RepositoryFile item to the List that we will eventually be returning (line 30). When we’re all done, we return the List (line 36).
Finally, we write the implementation code for our other OperationContact, approve. In addition, we’ll write a private function that will traverse all the tabs in my portal and find all the instances of Repository modules.
1: Public Function ApproveRepositoryFile(ByVal itemid As String) As Boolean _ 2: Implements IDNNService.ApproveRepositoryFile 3: 4: Dim results As Boolean 5: 6: Dim repoController As New Repository.RepositoryController 7: Try 8: repoController.ApproveRepositoryObject(Int32.Parse(itemid)) 9: results = True 10: Catch 11: results = False 12: End Try 13: 14: Return results 15: 16: End Function 17: 18: Private Function GetRepositoryModules(ByVal portalid As Integer) _ 19: As List(Of ModuleInfo) 20: 21: Dim mc As New ModuleController 22: Dim tc As New Entities.Tabs.TabController 23: Dim objModule As ModuleInfo 24: Dim lModules As New List(Of ModuleInfo) 25: 26: ' get a list of all of the Repository Modules installed in this 27: ' portal 28: 29: Dim repModInfo As ModuleDefinitionInfo = _ 30: ModuleDefinitionController.GetModuleDefinitionByFriendlyName( _ 31: "Repository") 32: 33: Dim package As PackageInfo = _ 34: PackageController.GetPackageByName("DotNetNuke.Repository") 35: 36: Dim tabsWithModule As IDictionary(Of Integer, Entities.Tabs.TabInfo) = _ 37: tc.GetTabsByPackageID(portalid, package.PackageID, False) 38: 39: ' first, get a list of all the tabs that contain at least one repository 40: For Each tab As Entities.Tabs.TabInfo In tabsWithModule.Values 41: 42: ' for each tab, get the repository module info 43: Dim modules As Dictionary(Of Integer, ModuleInfo) = _ 44: mc.GetTabModules(tab.TabID) 45: 46: For Each objModule In modules.Values 47: If objModule.ModuleDefID = repModInfo.ModuleDefID Then 48: lModules.Add(objModule) 49: End If 50: Next 51: 52: Next 53: 54: Return lModules 55: 56: End Function
1: Public Function ApproveRepositoryFile(ByVal itemid As String) As Boolean _
2: Implements IDNNService.ApproveRepositoryFile
4: Dim results As Boolean
6: Dim repoController As New Repository.RepositoryController
7: Try
8: repoController.ApproveRepositoryObject(Int32.Parse(itemid))
9: results = True
10: Catch
11: results = False
12: End Try
13:
14: Return results
15:
16: End Function
17:
18: Private Function GetRepositoryModules(ByVal portalid As Integer) _
19: As List(Of ModuleInfo)
20:
21: Dim mc As New ModuleController
22: Dim tc As New Entities.Tabs.TabController
23: Dim objModule As ModuleInfo
24: Dim lModules As New List(Of ModuleInfo)
25:
26: ' get a list of all of the Repository Modules installed in this
27: ' portal
28:
29: Dim repModInfo As ModuleDefinitionInfo = _
30: ModuleDefinitionController.GetModuleDefinitionByFriendlyName( _
31: "Repository")
32:
33: Dim package As PackageInfo = _
34: PackageController.GetPackageByName("DotNetNuke.Repository")
36: Dim tabsWithModule As IDictionary(Of Integer, Entities.Tabs.TabInfo) = _
37: tc.GetTabsByPackageID(portalid, package.PackageID, False)
38:
39: ' first, get a list of all the tabs that contain at least one repository
40: For Each tab As Entities.Tabs.TabInfo In tabsWithModule.Values
41:
42: ' for each tab, get the repository module info
43: Dim modules As Dictionary(Of Integer, ModuleInfo) = _
44: mc.GetTabModules(tab.TabID)
45:
46: For Each objModule In modules.Values
47: If objModule.ModuleDefID = repModInfo.ModuleDefID Then
48: lModules.Add(objModule)
49: End If
50: Next
51:
52: Next
53:
54: Return lModules
55:
56: End Function
Let’s see what we have so far…
For testing, I have placed a Repository module on a page in my web site and uploaded an item using a guest account so the upload will require moderation
And now, I log in using my admin account, and if I view my Repository module, you’ll see that there are no items showing, however I see there is 1 item requiring moderation.
So far so good … Now let’s test out our WCF service. If I open a browser and browse to my WCF service URL, and add the rest URI template that will return a list of unapproved RepositoryFile objects.
1: http://localhost/dnn531/desktopmodules/repository/ dnnservice.svc/rest/repository/get/0
1: http://localhost/dnn531/desktopmodules/repository/
dnnservice.svc/rest/repository/get/0
I will get the following XML returned
1: <ArrayOfRepositoryFile xmlns="http://schemas.datacontract.org/2004/07/" 2: xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 3: <RepositoryFile> 4: <Description><p>This is Whiskey Fabian. He's a 7 year old terrier mix</p></Description> 5: <ItemID>3</ItemID> 6: <Title>Whiskey</Title> 7: <User>JohnSmith</User> 8: </RepositoryFile> 9: </ArrayOfRepositoryFile>
1: <ArrayOfRepositoryFile xmlns="http://schemas.datacontract.org/2004/07/"
2: xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
3: <RepositoryFile>
4: <Description><p>This is Whiskey Fabian. He's a 7 year old terrier mix</p></Description>
5: <ItemID>3</ItemID>
6: <Title>Whiskey</Title>
7: <User>JohnSmith</User>
8: </RepositoryFile>
9: </ArrayOfRepositoryFile>
. Damn, this WFC stuff is easy!
We now have a WCF service that provides the 2 functions we need to do offline moderation.
In the next section, we’ll write a desktop application that will use our WCF service to check for uploaded files that require moderation, allow us to review the uploads and approve them, all without ever visiting our web site!
0 comment(s) so far...