Category Archives: Programming

WebsitePanel API: Adding a User

Following on from my initial post on how to connect to the WebsitePanel API from ASP.net, I will demonstrate how to write data to the API by creating a new WebsitePanel user account.

As before, my example is a simple ASP.Net Webform. For details of prerequisites and the development environment I am using, please read the first post.

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<fieldset>
        <legend>Create User</legend>
        <p>Fields in <b>bold</b> are required.</p>
        <table>
            <tr>
                <td>Owner:</td>
                <td><asp:DropDownList ID="ddlOwner" runat="server" /></td>
            </tr>
            <tr>
                <td>Load Error:</td>
                <td><asp:Literal ID="ltlLoadError" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Send Email Alert</b></td>
                <td><asp:CheckBox ID="chkSendEmail" runat="server" /></td>
            </tr>
            <tr>
                <td colspan="2"><hr /></td>
            </tr>
            <tr>
                <td><b>Role</b></td>
                <td>
                    <asp:DropDownList ID="ddlRole" runat="server">
                        <asp:ListItem Text="Administrator" Value="1" />
                        <asp:ListItem Text="Reseller" Value="2" />
                        <asp:ListItem Text="User" Value="3" />
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td><b>Status</b></td>
                <td>
                    <asp:DropDownList ID="ddlStatus" runat="server">
                        <asp:ListItem Text="Active" Value="1" />
                        <asp:ListItem Text="Suspended" Value="2" />
                        <asp:ListItem Text="Cancelled" Value="3" />
                        <asp:ListItem Text="Pending" Value="4" />
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td><b>Is Demo</b></td>
                <td><asp:CheckBox ID="chkIsDemo" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Username</b></td>
                <td><asp:TextBox ID="txtUsername" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Password</b></td>
                <td><asp:TextBox ID="txtPassword" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>First name</b></td>
                <td><asp:TextBox ID="txtFirstname" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Last Name</b></td>
                <td><asp:TextBox ID="txtLastname" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>Email</b></td>
                <td><asp:TextBox ID="txtEmail" Width="400" runat="server" /></td>
            </tr>
            <!-- Optional Parameters -->
            <tr>
                <td>Company Name</td>
                <td><asp:TextBox ID="txtCompanyName" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Address</td>
                <td><asp:TextBox ID="txtAddress" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>City</td>
                <td><asp:TextBox ID="txtCity" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>State</td>
                <td><asp:TextBox ID="txtState" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Zip/Postal Code</td>
                <td><asp:TextBox ID="txtPostcode" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Primary Phone</td>
                <td><asp:TextBox ID="txtPrimaryPhone" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Secondary Phone</td>
                <td><asp:TextBox ID="txtSecondaryPhone" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Fax</td>
                <td><asp:TextBox ID="txtFax" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>Instant Messenger</td>
                <td><asp:TextBox ID="txtInstantMessenger" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td>SecondaryEmail</td>
                <td><asp:TextBox ID="txtSecondaryEmail" Width="400" runat="server" /></td>
            </tr>
            <tr>
                <td><b>HTML Email</b></td>
                <td><asp:CheckBox ID="chkHtmlEmail" runat="server" /></td>
            </tr>
            <tr>
                <td>&nbsp;</td>
                <td><asp:Button ID="btnCreateUser" Text="Create User" runat="server" /></td>                
            </tr>
            <tr>
                <td colspan="2"><hr/></td>
            </tr>
            <tr>
                <td>Result:</td>
                <td><asp:Literal ID="ltlResult" runat="server" /></td>
            </tr>
            <tr>
                <td>Error:</td>
                <td><asp:Literal ID="ltlError" runat="server" /></td>
            </tr>
        </table>        
    </fieldset>

Code Behind:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
Imports System.Web.Services
Imports Microsoft.Web.Services3
Imports WebsitePanel.EnterpriseServer

Partial Class CreateUser
    Inherits System.Web.UI.Page

#Region "Events & Initial Loading"

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        If Not Page.IsPostBack Then PopulateUsersDdl()
    End Sub

    Private Sub PopulateUsersDdl()
        ' Note: Uses userId of 1 and returns all users

        ' Create proxy configurator object
        Dim config As New EnterpriseServerProxyConfigurator()
        config.EnterpriseServerUrl = INSERT_WEBSITEPANEL_ENTERPRISESERVER_URL
        config.Username = INSERT_WEBSITEPANEL_ENTERPRISESERVER_USERNAME
        config.Password = INSERT_WEBSITEPANEL_ENTERPRISESERVER_PASSWORD

        ' Create & configure proxy object
        Dim users As New WebsitePanel.EnterpriseServer.esUsers()
        config.Configure(users)

        ' Perform lookup
        Try
            Dim lst As WebsitePanel.EnterpriseServer.UserInfo() = users.GetUsers(1, True)
            ddlOwner.DataValueField = "UserId"
            ddlOwner.DataTextField = "Username"
            ddlOwner.DataSource = lst
            ddlOwner.DataBind()
            lst = Nothing
        Catch ex As Exception
            ' If error, output and hide create button
            ltlLoadError.Text = ex.Message
            btnCreateUser.Visible = False
        End Try

        ' Clean up
        ' - little bit of paranoia
        users.Dispose()
        users = Nothing
        config = Nothing
    End Sub

#End Region


#Region "User Creation"

    Protected Sub btnCreateUser_Click(sender As Object, e As EventArgs) Handles btnCreateUser.Click
        ' Reset Output
        ltlError.Text = ""
        ltlResult.Text = ""

        ' Get details
        Dim user As New WebsitePanel.EnterpriseServer.UserInfo
        user.OwnerId = ddlOwner.SelectedValue

        ' Mandatory Parameters
        user.RoleId = ddlRole.SelectedValue
        user.StatusId = ddlStatus.SelectedValue
        user.IsDemo = chkIsDemo.Checked
        user.Username = txtUsername.Text
        user.Password = txtPassword.Text
        user.FirstName = txtFirstname.Text
        user.LastName = txtLastname.Text
        user.Email = txtEmail.Text

        ' Optional Parameters
        user.CompanyName = txtCompanyName.Text
        user.Address = txtAddress.Text
        user.City = txtCity.Text
        user.State = txtState.Text
        user.Zip = txtPostcode.Text
        user.PrimaryPhone = txtPrimaryPhone.Text
        user.SecondaryPhone = txtSecondaryPhone.Text
        user.Fax = txtFax.Text
        user.InstantMessenger = txtInstantMessenger.Text
        user.HtmlMail = chkHtmlEmail.Checked
        user.SecondaryEmail = txtSecondaryEmail.Text

        ' Create proxy configurator object
        Dim config As New EnterpriseServerProxyConfigurator()
        config.EnterpriseServerUrl = ConfigurationManager.AppSettings("WebsitePanelServerUrl")
        config.Username = ConfigurationManager.AppSettings("WebsitePanelUsername")
        config.Password = ConfigurationManager.AppSettings("WebsitePanelPassword")

        ' Create & configure proxy object
        Dim users As New WebsitePanel.EnterpriseServer.esUsers()
        config.Configure(users)

        ' Attempt to save
        Try
            Dim result As Int32 = users.AddUser(user, chkSendEmail.Checked)

            If (result > 0) Then
                ltlResult.Text = String.Format("New UserId: {0}", result)
            Else
                Select Case result
                    Case -2
                        ltlResult.Text = "User creation failed:  Invalid OwnerId specified"
                    Case -100
                        ltlResult.Text = "User creation failed:  Username already exists"
                    Case -111
                        ltlResult.Text = "User creation failed:  Username not specified"
                    Case Else
                        ltlResult.Text = String.Format("User creation failed:  Result code of {0} returned", result)
                End Select
            End If
        Catch ex As Exception
            ltlError.Text = ex.Message
        End Try

        users = Nothing
        user = Nothing
    End Sub

#End Region

End Class

If successful, the value of result will be a positive integer which is the ID of the new user account. If a negative value is returned and error has occurred and the value is the error code. The error codes I have discovered so far are:

  • -2 = Invalid OwnerId specified
  • -100 = Username already exists
  • -111 = Username not specified

Testing Notes

  • Although the API documentation states that there are 10 required parameters, I have successfully created a new user by just specifying valid values for OwnerId, RoleId, StatusId and Username
  • An XML exception will occur if non-recognised values are specified for RoleId and StatusId

WebsitePanel API: Connecting from ASP.Net

This is the first in a series of posts explaining how to manipulate WebsitePanel using its’ Integration API from an ASP.Net website.

At Calzada Media, we’ve used WebsitePanel as our hosting control panel for many years now.  With only a few hiccups, it has done exactly what it says on the tin.  What it doesn’t do is in anyway integrate with any of the other systems on our website like billing.  Apart from the slight operational headache, this really doesn’t make for a great customer experience.  The eventual aim is to produce a helper library that will bridge this gap.

Sandbox (aka Bash’n’Crash) installation of WebsitePanel

You will need access to a WebsitePanel instance.  Unless you are supremely brave (and I’m not!) you should not use a production instance of WebsitePanel, but a bash’n’crash or sandbox installation.

My approach was to create a new Windows 2008 R2 virtual machine on my local Hyper-V server.  This VM was then configured as a broad representation of one of Calzada’s hosting servers (IIS + MySQL + WebsitePanel).  WebsitePanel was configured with the Portal listening on port 9001 and the EnterpriseServer on 9002.  Custom rules were added to the Windows Firewall to allow remote access on both of these ports.

If you don’t want to go down the VM route, you can install WebsitePanel on your PC if it is capable of running IIS  – typically Professional, Enterprise and Ultimate editions of Windows.

Required DLL files

Once you’ve installed WebsitePanel, you will need to copy three DLL files from c:\WebsitePanel\Portal\Bin into the bin directory of your ASP.net website:

  • Microsoft.Web.Service3.dll
  • WebsitePanel.EnterpriseServer.Base.dll
  • WebsitePanel.EnterpriseServer.Client.dll

Note: Although WebsitePanel uses Web Service Enhancements v3, you don’t need to install it on your development machine.  Just including the DLL should be sufficient.

Connecting to the WebsitePanel API and getting a list of users

Connecting to the WebsitePanel is actually fairly easy.  As WebsitePanel uses WSE3.0, not WCF, there is no need to create service references within Visual Studio – everything can be done programmatically.

1. In Visual Studio, create a new Web Form and paste the following code between the form tags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="GetUsers.aspx.vb" Inherits="GetUsers" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Get Users</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Users</h1>    
        <table>
            <tr>
                <td><asp:Button ID="btnReloadList" Text="Refresh" runat="server" /></td>
                <td>&nbsp;</td>
            </tr>
            <tr>
                <td>Result:</td>
                <td><asp:Literal ID="ltlResult" runat="server" /></td>
            </tr>
            <tr>
                <td>Error:</td>
                <td><asp:Literal ID="ltlError" runat="server" /></td>
            </tr>
        </table>

    </div>
    </form>
</body>
</html>

The code behind should be

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Imports System.Web.Services
Imports Microsoft.Web.Services3
Imports WebsitePanel.EnterpriseServer

Partial Class GetUsers
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        LoadUsersList()
    End Sub

    Protected Sub btnReloadList_Click(sender As Object, e As EventArgs) Handles btnReloadList.Click
        LoadUsersList()
    End Sub

    Private Sub LoadUsersList()
        ' Assume User ID 1

        ' Create proxy configurator object
        Dim config As New EnterpriseServerProxyConfigurator()
        config.EnterpriseServerUrl = INSERT_WEBSITEPANEL_ENTERPRISESERVER_URL
        config.Username = INSERT_WEBSITEPANEL_SERVERADMIN_USERNAME
        config.Password = INSERT_WEBSITEPANEL_SERVERADMIN_PASSWORD

        ' Create & configure proxy object
        Dim users As New WebsitePanel.EnterpriseServer.esUsers()
        config.Configure(users)

        ' Perform lookup
        Try
            Dim lst As WebsitePanel.EnterpriseServer.UserInfo() = users.GetUsers(1, True)
            gvUsers.DataSource = lst
            gvUsers.DataBind()
        Catch ex As Exception
            ltlError.Text = ex.Message
        End Try

        ' Clean up
        ' - little bit of paranoia
        users.Dispose()
        users = Nothing
        config = Nothing
    End Sub

End Class

Note: If an exception occurs, the error message will be written to the ltlError usercontrol.

Notes & Comments

The username and password is sent in the clear (via the SOAP header). Whilst this is not necessarily an issue for development work, it is of considerable importance when considering API usage in a production environment. If you are going to access the API remotely, you should consider additional measures (SSL, Firewall rules etc) to protect the crucial serveradmin account details.

Unlike my experience with WCF web services, I have not had to make any alterations or tweaks to the web.config to cater for SOAP message sizes etc. The calls just seem to work.

The Website API Documentation is somewhat sparse. I got the above to work through a combination of trawling through forums and experimentation in Visual Studio. There are a whole range of API calls that are simply not documented – like Hosting Plans – that I will be covering in future posts.

Detecting mobile browser in ASP.Net

Detecting mobile browsers can be a bit of pain.  Whilst it is relatively easy to get browser information through the User Agent string, it is not such a simple task to identify the client device type.  There is no defined property within the user agent field that says I’m a mobile device.  User Agent values differ widely between devices and browsers, so it is necessary to use some less than elegant detection code.

I recently added this detection functionality to the Camino CMS Framework, and Calzada’s preferred method is using regular expressions.  After some playing, we came up with the following regex:

(?i)(android(?:.+mobile)|applewebkit.+(?:\(KHTML, like Gecko){1}|avantgo|blackberry|blazer|docomo|elaine|fennec|htc|kindle|lge|linux.+armv|mmp|mms(?:.+midp){1}|mobile|mot(?:.+midp){1}|nintendo.+(?:3ds|wii)|nokia|nook|opera(?:.+mini|.+mobi){1}|palm|polaris|phone|ppc|psp|samsung|sec|sony|symbian|tablet|up\.browser|vodafone|wap|webos|windows(?:.+ce|.+mobile){1}|xda|xiino

A simplified ASP.Net detection code would look something like this:

Function IsMobileBrowser() As Boolean
Dim regex As New Text.RegularExpressions.Regex("(?i)(android(?:.+mobile)|applewebkit.+(?:\(KHTML, like Gecko){1}|" & _
"avantgo|blackberry|blazer|docomo|elaine|fennec|htc|kindle|lge|linux.+armv|mmp|mms(?:.+midp){1}|mobile|mot(?:.+midp){1}|" & _
"nintendo.+(?:3ds|wii)|nokia|nook|opera(?:.+mini|.+mobi){1}|palm|polaris|phone|ppc|psp|samsung|sec|sony|symbian|tablet|" & _
"up\.browser|vodafone|wap|webos|windows(?:.+ce|.+mobile){1}|xda|xiino", Text.RegularExpressions.RegexOptions.IgnoreCase)
Return regex.IsMatch(HttpContext.Current.Request.ServerVariables("http_user_agent").ToString)
End Function

There are a couple of key points to bear in mind with this method:

  1. It is not perfect.  No matter how you tailor and tweak the regular expression, you are inevitably going to get some false positives and false negatives.
  2. This is not a write and forget function.  As the number of mobile devices and manufacturers proliferate, the accuracy of the regular expression will diminish.  To compensate, it is necessary to regularly review, test and update where required.  To ensure that this is done, this process has been added this to the Camino development lifecycle.
  3. Not all mobiles or mobile users may wish to use mobile variant of a website.  Do bear in mind that some tablets have a sufficiently large screen to view the normal version of a website.
I am aware that this is not an elegant nor wholly accurate solution, and there are plenty of programmers out there that will view it as a kludge, but in many ways it is a product of its’ ingredients.  Until such time when all browsers will provide a property that simply states I’m running on a mobile device, then this will have to do.