Philip Lippard

Please say it ain't so

New Thinkpad W520

My new Thinkpad W520 has been a great investment for multiple virtual machine software development purposes.  Configured with an INTEL CORE I7-2920XM processor, solid state primary drive and 16GB of memory the multiple virtual machines run about twice as fast as my older Thinkpad T41p.

I still use my Seagate 500GB hybrid drive as my secondary drive, where I have all of my virtual machines located; the primary hard drive being used for the HOST OS only.

vmwareI am still using VMWare for virtualization, as was the case for the older Thinkpad T41p.  No need to consider a move away from VMWare Workstation 7.  I have been quite pleased with VMWare, after switching from the Microsoft Virtual PC product in 2009.   I also use VMWare ESXi Server for the hosting environment offered to my clients.

The primary reasons I purchased the W520 include the faster processor, as well as the solid state primary drive and of course the availability of USB3.  The faster processor plus USB 3 has reduced my full backup time from nine (9) to two (2) hours.

Twitter Authentication, OAuth and Discontinued Support for Basic Authentication

I was recently excited when I learned that the ELMAHopen source component (which I use as one of many components to monitor web site behavior) had the ability to send alerts to a twitter account.  I was then disappointed to learn that it no longer worked, because ELMAH was using Basic Authentication with the Twitter account’s user ID and password; and Twitter had disabled Basic Authenticationsince around 1-Sep-2010.  Twitter has opted to implement the more secure OAuth authentication scheme.   OAuthis a new open standard for providing delegated secure access to private resources, better defined as follows:

“…OAuth allows you to share your private resources (photos, videos, contact list, bank accounts) stored on one site with another site without having to hand out your username and password. There are many reasons why one should not share their private credentials. Giving your email account password to a social network  site so they can look up your friends is the same thing as going to dinner and giving your ATM card and PIN code to the waiter when it’s time to pay. Any restaurant asking for your PIN code will go out of business, but when it comes to the web, users put themselves at risk sharing the same private information. OAuth to the rescue.

oauth_diagram

After reading several articles on the web regarding OAuth, I learned quickly that replacing Basic Authentication with OAuth would be more involved than changing a few lines of code.  In fact, it would probably be necessary to find a suitable open source OAuth library to do the heavy lifting.  I found a couple of solutions from Shannon Whitley and also one called OAuth.net.

In so much as I was only looking to re-enable ELMAH Twitter support, I simply wanted to authenticate myself with Twitter and send an occasional direct user message.  OAuth.net provides support for the Consumer (client) requiring authentication as well as the Service Provider for web sites needing to implement secure delegation of credentials.  On the other hand the Shannon Whitney open source solution primarily addressed the needs of the Consumer (client).

After much downloading, assembling and debugging I settled on the Shannon Whitney open source solution, however parts of it were out of date and/or not working; such as the Twitter URLs being used.   I have enhanced and extended the Shannon Whitney solution to more than adequately meet my requirement of authenticating myself with Twitter.

To get startedone needs to register their applicationwith a Twitter user account from which one expects to authenticate on behalf of.   The output of this application registration will be a Consumer Key string value and a Consumer Secret value.   Using my enhanced and extended OAuth library the Consumer Key and Consumer Secret are used as the minimum requirements to obtain an Access Token Set; consisting of an Access Token string value and an Access Token Secret.  This Access Token Set are the base credentials to be used by one’s third party application to authenticate with Twitter.  This Access Token Set should be securely stored as is the case with any set of credentials.  The owning and delegator Twitter user can change his/her password at any time without compromising the ability of the third party application to authenticate.  The owning and delegator Twitter user can also revoke the privileges of the third party application without compromising his/her Twitter account for continued use.

What is needed for demo purposes is a program to request the Access Token Set and store it for future use and then another program to demonstrate how the Access Token Set can be used repeatedly for authentication.

At the end of this article is a download link to download a project which contains all sample programs and supporting libraries.  The sample web transaction below from this project shows the process of taking a Consumer Key and Consumer Secret, using the enhanced OAuth library and obtaining the Access Token Set (Access Token and Access Token Secret).

   1:  using System;
   2:  using System.Data;
   3:  using System.Configuration;
   4:  using System.Collections;
   5:  using System.Web;
   6:  using System.Web.Security;
   7:  using System.Web.UI;
   8:  using System.Web.UI.WebControls;
   9:  using System.Web.UI.WebControls.WebParts;
  10:  using System.Web.UI.HtmlControls;
  11:  using System.IO;
  12:  using System.Text;
  13:   
  14:  namespace oAuthExample
  15:  {
  16:      public partial class _Default : System.Web.UI.Page
  17:      {
  18:          protected void Page_Load(object sender, EventArgs e)
  19:          {
  20:              string url = string.Empty;
  21:              string xml = string.Empty;
  22:              oAuthTwitter oAuth = new oAuthTwitter();
  23:   
  24:              if (Request["oauth_token"] == null)
  25:              {
  26:                  //Redirect the user to Twitter for authorization.
  27:                  //Using oauth_callback for local testing.
  28:                  oAuth.CallBackUrl = this.Request.Url.AbsoluteUri;
  29:                  Response.Redirect(oAuth.AuthorizationLinkGet());
  30:              }
  31:              else
  32:              {
  33:                  //Get the access token and secret.
  34:                  oAuth.AccessTokenGet(Request["oauth_token"], Request["oauth_verifier"]);
  35:                  if (oAuth.TokenSecret.Length > 0)
  36:                  {
  37:                      // Save my Access Token Set - place in Web.config for use with
  38:                      // DefaultWithToken.aspx
  39:                      using (StreamWriter sw =
  40:                          new StreamWriter(Request.MapPath("~/TwitterCredentials"), false))
  41:                      {
  42:                          sw.WriteLine("Token=" + oAuth.Token);
  43:                          sw.WriteLine("TokenSecret=" + oAuth.TokenSecret);
  44:                          sw.Flush();
  45:                      }
  46:   
  47:                      url = "http://api.twitter.com/1/direct_messages/new.json";
  48:                      string postData = "user=@MyTwitterAccount" +
  49:                          "&text=" + 
  50:                          oAuth.UrlEncode("D @MyTwitterAccount - Access Token Obtained");
  51:                      xml = oAuth.oAuthWebRequest(oAuthTwitter.Method.POST, url, postData);
  52:                      apiResponse.InnerHtml = Server.HtmlEncode(xml);
  53:                  }
  54:              }
  55:          }
  56:      }
  57:  }

The above web transaction will essentially record the Access Token Set to the TwitterCredentials file of the root directory.  The Access Token and Access Secret from the TwitterCredentials file should now be placed into the AppSettings of the web.config and then the next web transaction will demonstrate using such credentials for repeated use:

   1:  using System;
   2:  using System.Data;
   3:  using System.Configuration;
   4:  using System.Collections;
   5:  using System.Web;
   6:  using System.Web.Security;
   7:  using System.Web.UI;
   8:  using System.Web.UI.WebControls;
   9:  using System.Web.UI.WebControls.WebParts;
  10:  using System.Web.UI.HtmlControls;
  11:   
  12:  namespace oAuthExample
  13:  {
  14:      public partial class DefaultWithToken : System.Web.UI.Page
  15:      {
  16:          // Direct Message API - http://dev.twitter.com/doc/post/direct_messages/new
  17:          // Limits (403) - http://support.twitter.com/forums/10711/entries/15364
  18:          // Also returns 403 when you send same message twice...
  19:          protected void Page_Load(object sender, EventArgs e)
  20:          {
  21:              string url = string.Empty;
  22:              string xml = string.Empty;
  23:              oAuthTwitter oAuth = new oAuthTwitter();
  24:              oAuth.Token = ConfigurationManager.AppSettings["accessToken"];
  25:              oAuth.TokenSecret = ConfigurationManager.AppSettings["accessTokenSecret"];
  26:   
  27:              url = "http://api.twitter.com/1/direct_messages/new.json";
  28:              string postData = "user=@MyTwitterAccount" +
  29:                          "&text=" + oAuth.UrlEncode("D @MyTwitterAccount - Hello World");
  30:              xml = oAuth.oAuthWebRequest(oAuthTwitter.Method.POST, url, postData);
  31:              apiResponse.InnerHtml = Server.HtmlEncode(xml);
  32:          }
  33:      }
  34:  }

The web.config is shown as follows:

   1:  <?xml version="1.0"?>
   2:  <configuration>
   3:    <appSettings>
   4:      <add key="consumerKey"
   5:           value="XXXXXXXXXXXXXXXXXXXXXXXXX"/>
   6:      <add key="consumerSecret"
   7:           value="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"/>
   8:      <add key="accessToken"
   9:           value="YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"/>
  10:      <add key="accessTokenSecret"
  11:           value="YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"/>
  12:    </appSettings>
  13:      <connectionStrings/>
  14:      <system.web>
  15:      <compilation debug="true"
  16:                   targetFramework="4.0" />
  17:          <authentication mode="Windows"/>
  18:          <pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/>
  19:    </system.web>
  20:  </configuration>

 

To protect the innocent, the web.config above does not show the actual Consumer Key and Consumer Secret as well as the actual Access Token and Access Token Secret.

That is it…Authentication with OAuth for Twitter…at least from the Consumer’s perspective.   Click HERE to download the project.

ESXi Server–Thin Provisioning

Thin-provisioning1Thin provisioning has been a feature within VMWare Workstation and VMWare ESXi Server for quite some while.   To manage the growth of space use/reuse within a VMWare VMDK for VMWare Workstation, one usually periodically compresses the VMDK.  Of course, such compression requires a shutdown of the virtual machine.

With ESXi Server based VMDKs, what I have found is that I have a preference for using Thin Provisioning, because when I take a snapshot, I follow taking such a snapshot by copying the VMDK to either another local or remote storage media, and of course such copying takes much less time with thinly provisioned VMDKs.   The copy of the VMDK is a logical copy, not a physical copy; meaning only the in-use data blocks get copied, with one noted exception.  This exception happens to be how free space is identified and re-used.  If a utility like SDELETE is used periodically to zero out the free space within the VMDK then such free space is not copied to the destination location.  For example; if after executing SDELETE used space is 25% of the VMDK, then the destination VMDK will occupy only 25% of the VMDK provisioned size.  Also, the source thinly provisioned VMDK may have grown to the full thinly provisioned size.

I generally allow my ESXi Server thinly provisioned VMDKs to grow to the full thinly provisioned size, and of course this requires that the production VMDKs will require the provisioned size, however there is tremendous advantage during the copying/backup process, as noted above.

CRUD, OData & WCF Data Services

As of late I have been working with WCF Data Services to evaluate its fitness when used with client based Javascript/jQuery applications.   I have been quite pleased at the results.  WCF Data Services exposes a CRUD OData (Open Data Protocol) interface which can be accessed and utilized from not only jQuery client based applications but also desktop WPF or Silverlight apps, console apps, Winform apps and practically any application capable of communicating with WCF REST services.  Early in my evaluation I did encounter the need to uninstall WebDAV, which caused a Bad Method status code 405 to be returned, however after uninstalling WebDAV and utilizing many Internet articles on OData and WCF, I concluded that the following examples are good working examples on using OData and CRUD.

Microsoft does have a higher level Javascript/AJAX based interface implemented in the Sys.components.openDataContextnamespace, however I choose to use the jQuery API to issue Http requests directly, as illustrated in the examples below…

The examples below are also referencing one important constant; my WCF Data Service URL prefix…

   1:  var dataService = "/jCredentials.svc";

 

CRUD – “C” is for Create

   1:  function InsertCredentialsPIN(selectedTreeNode, PINObj) {
   2:   
   3:      var url = dataService + "/Credentials_PINs";
   4:   
   5:      var json = JSON.stringify(PINObj);
   6:   
   7:      $.ajax({
   8:          url: url,
   9:          data: json,
  10:          type: "POST",
  11:          contentType: "application/json; charset=utf-8",
  12:          dataType: "json",
  13:          success: function (result) {
  14:              // Do something
  15:          },
  16:          error: function (result) {
  17:              alert("PIN Insert Failure - Status Code=" + 
  18:                  result.status + ", Status=" + result.statusText);
  19:          }
  20:      });
  21:  }

CRUD – “R” is for Read

   1:  function GetPINTree(orderBy) {
   2:   
   3:      var url = null;
   4:      if (orderBy == null) {
   5:          url = dataService + "/Credentials_PINTree";
   6:      }
   7:      else {
   8:          url = dataService + "/Credentials_PINTree?$orderby=" + orderBy;
   9:      }
  10:   
  11:      $.ajax({
  12:          url: url,
  13:          type: "GET",
  14:          contentType: "application/json; charset=utf-8",
  15:          dataType: "json",
  16:          success: function (result) {
  17:              // Do something
  18:          },
  19:          error: function (result) {
  20:              alert("PINTree Get Failure - Status Code=" + 
  21:                      result.status + ", Status=" + result.statusText);
  22:          }
  23:      });
  24:  }

CRUD – “U” is for Update

   1:  function UpdatePINTree(selectedTreeNode, PINTreeObj) {
   2:   
   3:      var url = dataService + "/Credentials_PINTree(guid'" + 
   4:                          selectedTreeNode.get_value() + "')";
   5:   
   6:      var json = JSON.stringify(PINTreeObj);
   7:   
   8:      $.ajax({
   9:          url: url,
  10:          data: json,
  11:          type: "PUT",
  12:          contentType: "application/json; charset=utf-8",
  13:          dataType: "json",
  14:          success: function (result) {
  15:              // Do something
  16:          },
  17:          error: function (result) {
  18:              alert("PIN Tree Update Failure - Status Code=" + 
  19:                  result.status + ", Status=" + result.statusText);
  20:          }
  21:      });
  22:  }

CRUD – “D” is for Delete

   1:  function DeletePIN(selectedTreeNode) {
   2:   
   3:      var url = dataService + "/Credentials_PINs(guid'" + 
   4:                              selectedTreeNode.get_value() + "')";
   5:   
   6:      $.ajax({
   7:          url: url,
   8:          type: "DELETE",
   9:          contentType: "application/json; charset=utf-8",
  10:          dataType: "json",
  11:          success: function (result) { 
  12:              // Do something
  13:          },
  14:          error: function (result) {
  15:              alert("PIN Delete Failure - Status Code=" + 
  16:                      result.status + ", Status=" + result.statusText);
  17:          }
  18:      });
  19:  }

jQuery and WCF Data Services OData (Open Data Protocol)

I just spend two days trying to figure out why I was getting a HTTP status code 405 (method not allowed) IIS 7.5 response when attempting a jQuery based PUT method to update an existing entity.  As it turns out I tried it with the internal VS 2010 web server and it was working OK.  This helped me to focus my Google searches a bit and I finally narrowed the problem down to having WebDAV Publishing installed on my Windows 7 development virtual machine.  I uninstalled WebDAV and the OData data service now works fine.

I found the WebDAV mention in this post… http://social.msdn.microsoft.com/Forums/en/windowsazure/thread/a22b9e60-8353-40c7-af3e-69a8f18240c1

OData_logo_MS_small

Backing up to the Cloud

As a result of my recent migration to VMWare ESX Server for my offsite hosted servers, I also needed to consider a backup strategy.  In addition to having a local daily backup option, I also wanted a secondary cloud backup strategy.  I selected the products available from Cloudberry Labs; a software company based out of St. Petersburg Russia.

Content_01 There is a freeware edition of the Cloudberry products, however I selected the Cloudberry Explorer Pro and Cloudberry Online Backup Server Edition.  I have licensed the Online Backup Server Edition for each of my VMWare virtualized servers, and at around 2am each morning Online Backup Server Edition executes their user defined backup plan.

powered_by Cloudberry products support backing up to the cloud services of Amazon S3 (Simply Storage Service), Microsoft Azure, the emerging Google Storage Service and Dunkel Storage (a Amazon S3 compatible service based out of Germany).

The Cloudberry Explorer Pro and Online Backup products both support compression and encryption as features for any backup plan.  The Cloudberry compression feature reduces the amount of data being backed up on an average of 66%; effectively reducing the Amazon S3 storage charge from 15 cents down to 5 cents per GB.   The encryption feature provides an additional of privacy and security for your data.

Using Paypal Buy Now Buttons with ASP.NET

I usually use Paypal Web Services for ECommerce, however for the occasional ECommerce merchant, Paypal Web Services may not be appropriate because of the minimum monthly fee.  Depending on one’s preferences, Paypal Buy Now buttons may be suitable for ECommerce.  Buy Now buttons incur no minimum monthly fee and involve conducting a copy/paste operation of HTML into one’s HTML page, as illustrated below:

   1:      <form action="https://www.paypal.com/cgi-bin/webscr" 
   2:          method="post">
   3:          <input type="hidden" 
   4:              name="cmd" 
   5:              value="_s-xclick" />
   6:          <input type="hidden" 
   7:              name="hosted_button_id" 
   8:              value="F4E333HY55FXX" />
   9:          <input type="image" 
  10:              src="https://www.paypal.com/en_US/i/btn/btn_buynow_SM.gif" 
  11:              border="0" 
  12:              name="submit" alt="PayPal - The safer, easier way to pay online!" />
  13:          <img alt="" 
  14:              border="0" 
  15:              src="https://www.paypal.com/en_US/i/scr/pixel.gif" 
  16:              width="1" 
  17:              height="1" />
  18:      </form>

With ASP.NET the above Paypal Buy Now HTML snippet is problematic because ASP.NET WebForms is limited to a single “form” tag definition.  As an alternative one can use the revised HTML snippet to accomplish the same task.

   1:          <input type="hidden" 
   2:              name="cmd" 
   3:              value="_s-xclick" />
   4:          <input type="hidden" 
   5:              name="hosted_button_id" 
   6:              value="F4E333HY55FXX" />
   7:          <asp:ImageButton runat="server" 
   8:              ImageUrl="https://www.paypal.com/en_US/i/btn/btn_buynow_SM.gif" 
   9:              PostBackUrl="https://www.paypal.com/cgi-bin/webscr" />

You will note that the above HTML snippet has been revised such that the forms tag is removed and we are conducting a postback using the ASP.NET ImageButton control.  This works fine provided we have only a single Paypal Buy Now button per WebForm page.

Let’s consider the situation where we have several product definitions defined within SQL Server along with a Paypal Buy Now HTML button definition within SQL for each product.   Let’s also say we are attempting to use an ASP.NET ListView for conducting runtime data binding, illustrated as follows:

   1:  <asp:ListView GroupItemCount="3"
   2:      DataSourceID="dataSource"
   3:      DataKeyNames="GuidKey"
   4:      runat="server">                                
   5:      <LayoutTemplate>
   6:   
   7:          <table id="productTable" runat="server" 
   8:              cellpadding="0"
   9:              cellspacing="0"  
  10:              border="0">                                            
  11:                                  
  12:              <tr runat="server" id="groupPlaceholder" />    
  13:                                                  
  14:          </table>
  15:      </LayoutTemplate>
  16:      <GroupTemplate>
  17:          <tr>
  18:              <td runat="server" id="itemPlaceholder" />
  19:          </tr>
  20:      </GroupTemplate>
  21:      <ItemTemplate>                                            
  22:          <td>
  23:              <div>
  24:                  <div>
  25:                      <asp:Literal runat="server" 
  26:                          Text='<%# Eval("Title") %>' />    
  27:                  </div>
  28:                  <div>
  29:                      <asp:Literal runat="server" 
  30:                          Text='<%# Eval("Description") %>' />
  31:                  </div>    
  32:                  <div>
  33:                      <asp:Literal runat="server" 
  34:                          Text='<%# InjectImage(Eval("Url")) %>'  />    
  35:                  </div>
  36:                  <div>
  37:                      <asp:Literal runat="server" Text='<%# Eval("ButtonHTML") %>' />
  38:                      <asp:ImageButton runat="server" 
  39:                          ImageUrl="~/Themes/Images/ECommerce-Buttons/button-add-cart-blue.gif" 
  40:                          PostBackUrl="https://www.paypal.com/cgi-bin/webscr" />
  41:                  </div>
  42:              </div>                                
  43:          </td>
  44:      </ItemTemplate>    
  45:  </asp:ListView>

The above illustration assumes that the ButtonHTML SQL field being bound contains the Paypal Buy Now HTML snippet; the snippet that has been revised for ASP.NET.  The above implementation is problematic because Paypal will encounter errors when reading the Paypal hidden fields, because there will be multiple Paypal hidden fields with the same “name” attribute; “cmd” and “hosted_button_id”, etc.

The most effective means that I have found to get around this problem is to continue to define the revised Paypal Buy Now HTML snippet as we doing above, because doing so will simplify the copy/paste operation from Paypal to our SQL Server app.  Ultimately, what we want to do is to transform all hidden field “name” attributes to “id” attributes during WebForm start-up, immediately after the ListView is rendered.  Using jQuery, we transform the productTable ListView as follows:

   1:   
   2:   
   3:  function OnAjaxLoad() {
   4:   
   5:     
   6:      jQuery(document).ready(function () {
   7:          InitializePaylBuyNowButtons();
   8:      });
   9:  }
  10:   
  11:  function InitializePaylBuyNowButtons() {
  12:   
  13:      jQuery("table[id$='_productTable']").children('tbody').children('tr').each(function () {
  14:          jQuery(this).children('td').each(function () {
  15:              jQuery(this).children('div').children('#paypalButton').each(function () {
  16:   
  17:                  var productContainer = jQuery(this);
  18:   
  19:                  // initialize standard Paypal Buy Now hidden fields - 
  20:                  // move name attribute value to id attribute
  21:                  // so these hidden fields will not be visible to Paypal
  22:                  productContainer.children('input:hidden').each(function () {
  23:   
  24:                      switch (jQuery(this).attr("name")) {
  25:                          case "cmd":
  26:                          case "hosted_button_id":
  27:                          case "currency_code":
  28:                              jQuery(this).attr("id", jQuery(this).attr("name"));
  29:                              jQuery(this).attr("name", "");
  30:                              break;
  31:                          default:
  32:                              break;
  33:                      }
  34:                  });
  35:   
  36:                  // find possible drop-down - used for multiple prices - 
  37:                  // do same name ==> id move
  38:                  productContainer.children('table').children('tbody').children('tr')
  39:                      .children('td').children('input:hidden').each(function () {
  40:   
  41:                      switch (jQuery(this).attr("name")) {
  42:                          case "on0":
  43:                              jQuery(this).attr("id", jQuery(this).attr("name"));
  44:                              jQuery(this).attr("name", "");
  45:                              break;
  46:                          default:
  47:                              break;
  48:                      }
  49:                  });
  50:   
  51:                  // find possible drop-down - used for multiple prices - 
  52:                  // do same name ==> id move
  53:                  productContainer.children('table').children('tbody').children('tr')
  54:                      .children('td').children('select').each(function () {
  55:   
  56:                      switch (jQuery(this).attr("name")) {
  57:                          case "os0":
  58:                              jQuery(this).attr("id", jQuery(this).attr("name"));
  59:                              jQuery(this).attr("name", "");
  60:                              break;
  61:                          default:
  62:                              break;
  63:                      }
  64:                  });
  65:   
  66:              });
  67:          });
  68:   
  69:      });
  70:  }
  71:   
  72:  Sys.Application.add_load(OnAjaxLoad);

After we have transformed all Paypal Buy Now button hidden fields, we want to revise our ListView definition to accommodate the above jQuery start-up transformation as follows:

   1:  <asp:ListView GroupItemCount="3"
   2:      DataSourceID="dataSource"
   3:      DataKeyNames="GuidKey"
   4:      runat="server">                                
   5:      <LayoutTemplate>
   6:   
   7:          <table id="productTable" runat="server" 
   8:              cellpadding="0"
   9:              cellspacing="0"  
  10:              border="0">                                            
  11:                                  
  12:              <tr runat="server" id="groupPlaceholder" />    
  13:                                                  
  14:          </table>
  15:      </LayoutTemplate>
  16:      <GroupTemplate>
  17:          <tr>
  18:              <td runat="server" id="itemPlaceholder" />
  19:          </tr>
  20:      </GroupTemplate>
  21:      <ItemTemplate>                                            
  22:          <td>
  23:              <div>
  24:                  <div>
  25:                      <asp:Literal runat="server" 
  26:                          Text='<%# Eval("Title") %>' />    
  27:                  </div>
  28:                  <div>
  29:                      <asp:Literal runat="server" 
  30:                          Text='<%# Eval("Description") %>' />
  31:                  </div>    
  32:                  <div>
  33:                      <asp:Literal runat="server" 
  34:                          Text='<%# InjectImage(Eval("Url")) %>'  />    
  35:                  </div>
  36:                  <div id="paypalButton">
  37:                      <asp:Literal runat="server" Text='<%# Eval("ButtonHTML") %>' />
  38:                      <asp:ImageButton runat="server" 
  39:                          ImageUrl="~/Themes/Images/ECommerce-Buttons/button-add-cart-blue.gif" 
  40:                          PostBackUrl="https://www.paypal.com/cgi-bin/webscr" 
  41:                          OnClientClick="PaypalBuyNowProductSelected(this);" />
  42:                  </div>
  43:              </div>                                
  44:          </td>
  45:      </ItemTemplate>    
  46:  </asp:ListView>

You will note that we have revised the following:

  • Added an OnClientClick event handler ( PaypalBuyNowProductSelected(this) ) on the ImageButton ASP.NET control, the purpose of which will be to reverse the effect of the hidden fields start-up transformation, but only for the single selected product.  This will ensure that only a single product is communicating with Paypal during the Buy Now postback operation.
  • Add the “id=paypalButton” attribute to the DIV section containing the Paypal Buy Now HTML snippet for each product and the ImageButton control.  This revision makes the jQuery parsing easier.

Now we need to drop in a jQuery ImageButton OnClientClick event handler:

   1:  function PaypalBuyNowProductSelected(o) {
   2:   
   3:      var productContainer = jQuery(o).parent('div');
   4:   
   5:      // activate Paypal hidden fields to properly 
   6:      // associate Paypal product being selected
   7:      productContainer.children('input:hidden').each(function () {
   8:   
   9:          switch (jQuery(this).attr("id")) {
  10:              case "cmd":
  11:              case "hosted_button_id":
  12:              case "currency_code":
  13:                  jQuery(this).attr("name", jQuery(this).attr("id"));
  14:                  break;
  15:              default:
  16:                  break;
  17:          }
  18:      });
  19:   
  20:      // find possible drop-down - used for multiple prices - 
  21:      // locate hidden input field
  22:      productContainer.children('table').children('tbody')
  23:          .children('tr').children('td')
  24:          .children('input:hidden').each(function () {
  25:   
  26:          switch (jQuery(this).attr("id")) {
  27:              case "on0":                 
  28:                  jQuery(this).attr("name", jQuery(this).attr("id"));
  29:                  break;
  30:              default:
  31:                  break;
  32:          }
  33:      });
  34:   
  35:      // find possible drop-down - used for multiple prices - 
  36:      // locate select field
  37:      productContainer.children('table').children('tbody')
  38:          .children('tr').children('td')
  39:          .children('select').each(function () {
  40:   
  41:          switch (jQuery(this).attr("id")) {
  42:              case "os0":                 
  43:                  jQuery(this).attr("name", jQuery(this).attr("id"));
  44:                  break;
  45:              default:
  46:                  break;
  47:          }
  48:      });
  49:   
  50:      return true;
  51:  }

You will note that the OnClientClick event handler is simply transforming Paypal hidden field “id” attributes to “name” attributes for the single product being selected; thus reversing the effect of the original WebForm start-up transformation for the single product only.

That is it.  Now we have working ASP.NET and jQuery code such that Paypal Buy Now buttons will work with ASP.NET and multiple product definitions.  If we use other Paypal hidden fields they can be manipulated in the same manner as the “cmd” and “hosted_button_id” hidden fields.

ESX Server Snapshot Backups

One of the more compelling reasons to use ESX/ESXi Server for virtual server management is the feature called Snapshot backups.  Snapshot backups provide the ESX/ESXi Server administrator with the ability to essentially freeze a virtual machine (VMDK virtual server) at a point in time while scheduled updates or upgrades are being attempted.  Snapshot backups are managed using the VMWare VI Client, discussed in a earlier post.

Take Snapshot When a snapshot is taken all updates (regardless of origin) which occur to the virtual machine will be written to a set of snapshot files, rather than the VMDK virtual machine image; thus providing the administrator with the opportunity to copy the VMDK virtual machine image.  A snapshot can be taken for two reasons;  1) if an administrator wants to copy the VMDK virtual machine, or 2)  if the administrator wants to apply updates or upgrades (update from Microsoft, application upgrades, etc) to the virtual machine. 

Deleting Snapshot Once the VMDK virtual machine has been copied successfully, using simply copy/paste commands against the VMDK Virtual machine, or once the updates/upgrades have been verified as having applied successfully, then the snapshot is customarily deleted.  Snapshot deletion will force ESX/ESXi Server to apply all queued updates/ upgrades (queued in the snapshot set of files) to the actual VMDK virtual machine.

Reverting to Snapshot If the updates/upgrades failed for some reason then the administrator can revert the VMDK virtual machine back to the point in time when the snapshot was originally taken.  If the VMDK virtual machine is reverted to the point in time when the snapshot was originally taken then all updates/upgrades after that point in time will be lost, including the failed updates/ upgrades.

Snapshot backups are indeed a handy tool for virtual machine management.   How many times have you applied an upgrade only to sadly find out that the upgrade failed and it needed to be backed out.

ESX/ESXi Server – Single Server Management Tools

The functionality and performance of VMware ESX and ESXi are the same; the difference between the two hypervisors resides in their architecture and operational management.  VMware ESXi is the latest hypervisor architecture from VMware. It has an ultra thin Linux kernel footprint with no reliance on a general-purpose OS, setting a new bar for security and reliability. The small footprint and hardware-like reliability of VMware ESXi enable it to also be available preinstalled on industry standard x86 servers.

ESXi Server is also available as a no-cost entry level hypervisor.  During my deployment of ESXi Server I found a lack of available information on when ESXi Server is free and at what point ESXi Server incurs a license fee.   Even internal VMWare personnel were unclear about the differentiation between an ESXi no-cost option versus ESXi Server available on a license fee basis. 

One can download ESXi Server for a 60 day evaluation trial period.  The 60 day trial provides one with a restricted function ESXi Server.  It is not always clear what features are restricted, however during my use it appears that functions relating to cloning and backing up of virtual machines are indeed restricted.  One can register their no-cost ESXi Server license to ensure one has a working ESXi Server longer than 60 days, however the functions relating to cloning and backup are still restricted.   The functions relating to cloning and backup appear to only be available once one licenses a VMWare product, such as VSphere Essentials, which includes what is referred to a VMWare Consolidated Backup (VCB).

VI Client The VI Client is a desktop application used to communicate with ESXi Server.   The VI Client can be used to configure an ESXi Server, monitor ESXi Server performance and take snapshot backups, however VI Client cannot easily be used to backup a virtual machine outside of a single ESXi Server environment.   With the VI Client, the best backup one can hope to achieve is taking a snapshot backup to an alternate hard drive, also attached to the same ESXi Server.   ESXi Server backups will be explorer in detail in another blog post.

Other available interfaces for managing the ESXi Server include the VI Command Level interface, essentially a batch like command level interface; commands being issued from a PC to the ESXi Server.

A Secure Shell (SSH) interface can also be enabledvia the ESXi console.   Once enabled, programs like PuTTY and WinSCP can be used to communicate with the ESXi Server.  SSH is available for diagnostic purposes (in theory), however I found the SSH interface to be essential for a stable on-going ESXi Server environment.  I have found PuTTY necessary for monitoring the Linux based file system.  I have had problems where the Linux based file system will fill up due to ESXi log file usage.   I have to login via PuTTY and move log file out of the base Linux file system using either Linux commands or WinSCP.  Once the Linux based file system starts filling up the ESXi Server will either become quite sluggish or an unexpected re-boots may occur.

In summary, I find essential single ESXi Server management tools to be VI Client, PuTTY and WinSCP.   With SSH enabled, I had very little use for the VI Client command level interface.  Having SSH enabled allows me to use PuTTY for what is essentially ESXi Server console access.   I can issue Linux commands and ESXi Server specific commands directly.

VMWare ESX Server Deployment

products_esx_diagram

Over the next several weeks I will be making posts related to my recent experience of deploying VMWare ESX Server.  ESX Server is a bare metal hypervisor offered by VMWare.  Supported guest operating systems include just about any Windows or Linux based server based OS.  For me, I am initially deploying four instances of Windows Web Server 2008 R2.

My principal objective in deploying ESX Server was/is the ability to snapshot backup a running guest OS, thus simplifying software upgrades.  Other reasons include the ability to start-up a new guest server OS for any reason without the need for deploying new hardware.

The principal candidates in the virtualization marketplace include VMWare, Microsoft and the Citrix XEN offering.  I have always found Microsoft to be several steps behind the marketplace with respect to virtualization technology.  I was quite disappointed with Microsoft’s virtualization offerings for the desktop at the time of the Windows 7 release, and as a result I moved from Microsoft Virtual PC 2007 to VMWare’s Workstation 7.0 for my development environment.   I had my share of problems in moving to VMWare Workstation, however I now consider it a smart move.  Many of the issues encountered with the VMWare Workstation migration are outlined in my Sep/Oct 2009 blog posts.

I also had many problems in migrating to VMWare ESX Server, however again, I think it was worth the move.   The forthcoming blog posts will discuss some of the challenges.