Category Archives: Hosting Control Panels

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.

Configuring the holding or parking page in WebsitePanel

Configuring the default holding page in WebsitePanel can be a little time-consuming, primarily because I can never remember where the configuration options are.  To change the holding page, or Parking Page in WSP parlance, do the following:

  1. Login to WebsitePanel
  2. Click on the Policies link at the bottom right of the page.
  3. Click on WEB Policy link
  4. Under the section Parking Page enter the code for your holding page
  5. .Click on the Save button at the bottom of page once done.

 

 

WebsitePanel and MySQL 5.5 Configuration Error. Again.

A couple of months ago I wrote about an error we encountered with WebsitePanel and MySQL 5.5.  Essentially, this boiled down to WebsitePanel only recognising specific versions of the MySQL .Net Connector.  The solution was actually very simple: tweak some entries in the web.config files.

Since then, we’ve upgraded WebsitePanel from 1.2.1 to version 2 (highly recommended) without any problems.  A couple of weeks ago, we upgraded the MySQL .Net connector for a new project and promptly got the following error message:

System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---&gt; System.IO.FileNotFoundException: Could not load file or assembly 'MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' or one of its dependencies. The system cannot find the file specified.

As before, this problem is very easy to fix.  Simply change the following entries in the web.config in both WebsitePanel’s server and portal components (the default paths are c:\websitepanel\server andc:\websitepanel\portal respectively).  If these entries don’t exist, create them.

<runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
        <dependentAssembly> 
            <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" /> 
            <bindingRedirect oldVersion="0.0.0.0-6.3.7.0" newVersion="6.5.4.0" /> 
        </dependentAssembly> 
    </assemblyBinding> 
</runtime>

To

<runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
        <dependentAssembly> 
            <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" /> 
            <bindingRedirect oldVersion="0.0.0.0-6.5.4.0" newVersion="6.6.4.0" />
        </dependentAssembly> 
    </assemblyBinding> 
</runtime>

The only line that changes begins with bindingRedirect and you need to change the value of newVersion to that of the version of the MySQL .Net Connector you have installed.  In this case, 6.6.4.0.

The mistake here – and it was my mistake – was assuming that a known issue with WebsitePanel had been addressed in the latest version.  Needless to say, there are now specific notes regarding upgrading the MySQL .Net Connector.

On a side note: I’ve been developing .Net websites using MySQL for well over 8 years now.  I’ve very rarely run into this problem and it nearly has always occurred when I have deployed a website that utilises a much newer version of the connector than is installed on the server.  One thing that I never done is to explicitly define a version of the .Net Connector.  Whilst this may be considered not to be best practice in some quarters, it does go some where to accommodating differing installed versions of the connector, especially on shared hosting providers (i.e. servers outside of my direct control).

WebsitePanel and MySQL 5.5 Configuration Error

If you try and configure the MySQL 5 service in WebsitePanel 1.2.1 you may get the following error message:

System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.IO.FileNotFoundException: Could not load file or assembly 'MySql.Data, Version=6.3.7.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' or one of its dependencies. The system cannot find the file specified.

This error is down to WebsitePanel looking for a specific version (6.3.7.0) of the MySQL .Net Connector.  In all likelihood, you will have installed either a later version (at the time of writing it is 6.5.4).

Thankfully, this is error is easy to fix and only requires modifying a couple of configuration files.  Add the following code snippet at the end of the web.config files before the closing </configuration> tag for both WebsitePanel’s server and portal components (the default paths are c:\websitepanel\server and c:\websitepanel\portal respectively):

1
&lt;runtime&gt;
1
&lt;assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"&gt;
1
&lt;dependentAssembly&gt;
1
&lt;assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" /&gt;
1
&lt;bindingRedirect oldVersion="0.0.0.0-6.3.7.0" newVersion="6.5.4.0" /&gt;
1
&lt;/dependentAssembly&gt;
1
&lt;/assemblyBinding&gt;
1
&lt;/runtime&gt;

If you are using a version of the .Net Connector later than 6.5.4.0, then do amend the bindingRedirect newVersion property.

Starting off with WebsitePanel

There are a whole gamut of hosting control panels available.  I have used quite a few of them, and to be fair, none have really satisfied the criteria of being easy to use, powerful and responsive.  The only one that I have vaguely liked to date is Helm 3.  The less that said about Helm 4, the better.

Now, I will accept that I am being a little unfair with the last criteria.  A substantial proportion of a control panel’s response time is taken up by calls on other services, often based on separate hardware.  When you take this into consideration, Hosting Control Panels (HCPs) do succeed in corralling together and presenting management capabilities for multiple services (often from differing providers) in a single interface.  What very few have done is present an interface that is easy to use both from an administrative and end-user perspective.

And this is the critical point.  HCPs are primarily for use by end-users, not administrators, but this is something that is still regrettably not addressed by developers.  End-users often have a poor level of technical literacy, and very few are prepared to wade through tiered menu levels following obscure technobabble.  They just want to create an e-mail account.  Why can’t they just log in and get presented with an Add E-mail Account link?

At Calzada, we are determined to make life simpler for our hosting customers, and admittedly, ourselves.  A good HCP will not only make life easier for our customers, but potentially reduce the number of control panel related support queries.  Therefore, the choice of hosting control panel was critical.  I also have to admit that price is also a controlling factor.  We are a small company, and cannot realistically justify some of the prices being charged for the likes of Plesk and cPanel/WHM.

Unlike in *nix world, open source Windows HCPs are a rarity.  The only real choices are zPanel or WebsitePanel.  After some testing, we opted for the latter as WebsitePanel supported a wider variety of applications than zPanel, and did appear to be more mature.

WebsitePanel is not perfect and there is undoubtedly room for improvement in some areas.  We have experienced a number of glitches that have required workarounds – more posts to come on these – but the overall experience has been fairly good.  The installation program worked like a dream and was noticeably quicker than some of its’ commercial competitors.  I have to admit to warming to WebsitePanel; it does what it is expected to do and it is fairly easy to use for both administrative and end-user purposes.