One of the ideas we had for this blog was to post the lessons we had learnt during the development of Cart Viper. Although we feel as a CMS DotNetNuke is really good, as a platform to develop on the documentation is somewhat lacking. So the aim of this and future posts is to try and save you some time and hours of pulling your hair out if you are developing modules for the DotNetNuke platform by recounting the issues with the framework we have encountered.

In Cart Viper we have made extensive use of jQuery Ajax calls to provide a rich user experience. In order to achieve this we call a HttpHandler which in turn delegated the request to the appropriate class that would then process the request and return the correctly formatted JSON result. It was whilst in this class we ran into an issue.

Whilst in the HttpContext whenever you required the current PortalSettings object you could simple call:

   1: PortalSettings portalSettings = Globals.GetPortalSettings();

However as we were not in the HttpContext, PortalSettings was always null. This left us with the problem of how to instantiate the PortalSettings object.

One solution would be to pass in the PortalID as a parameter into the jQuery Ajax request, however a user could easily see the JSON request using firebug and then attempt to pass in a different PortalID into your HttpHandler, which in turn could then lead to cross portal updates. We did not like the security risks in this method so we thought how could we securely pass the current PortalID to the required class?

The solution we devised involved creating a base class that all .ascx controls we use within Cart Viper would implement:

   1: public class StoreControlBase : PortalModuleBase
   2: {
   3:     public StoreControlBase()
   4:     {
   5:     }
   6: }

In this base class we inserted the following method:

   1: /// <summary>
   2: /// Writes the portalId to an encrypted cookie
   3: /// </summary>
   4: /// <remarks>this is the used by ajax to determine the correct portalId</remarks>
   5: protected void WritePortalIdCookie()
   6: {
   7:     HttpCookie cookie = HttpContext.Current.Request.Cookies["YOUR PORTAL COOKIE"];
   8:     if (cookie == null)
   9:     {
  10:         cookie = new HttpCookie("YOUR PORTAL COOKIE");
  11:     }
  13:     string cookieValue;
  15:     if (SymmetricHelper.CanSafelyEncrypt)
  16:         cookieValue = SymmetricHelper.Encrypt(this.PortalId.ToString());
  17:     else
  18:         cookieValue = PortalId.ToString();
  20:     cookie["PortalId"] = cookieValue;
  22:     HttpContext.Current.Response.Cookies.Add(cookie);
  23: }

Then in each of the .ascx controls that make a jQuery AJAX request we simple add the following line to the page load event:

   1: protected void Page_Load(object sender, System.EventArgs e)
   2: {
   3:    //write out the portal cookie so we have it for the ajax
   4:    base.WritePortalIdCookie();
   6:    //page load as normal
   7: }

Then in the class it is just a case of getting the PortalID that is stored securely in the cookie and populating the associated PortalSettings object:

   1: /// <summary>
   2: /// Readonly access to the PortalSettings object for the
   3: /// current portal context
   4: /// </summary>
   5: protected PortalSettings PortalSettings
   6: {
   7:     get
   8:     {
   9:         HttpCookie cartCookie = HttpContext.Current.Request.Cookies["YOUR COOKIE NAME"];
  10:         if (cartCookie != null)
  11:         {
  12:             string pid = cartCookie["PortalId"];
  13:             if (SymmetricHelper.CanSafelyEncrypt &&
  14:                 !string.IsNullOrEmpty(pid))
  15:             {
  16:                 try
  17:                 {
  18:                     pid = SymmetricHelper.Decrypt(pid);
  19:                     return new PortalSettings(Int32.Parse(pid));
  20:                 }
  21:                 catch (Exception ex)
  22:                 {
  23:                     //the portal cookie is correct, rethrow
  24:                     throw ex;
  25:                 }
  26:             }
  27:         }
  29:         throw new ApplicationException("Portal cookie is not valid");
  30:     }
  31: }

This property should then return a fully populated PortalSettings object.which you can then query to get specific information relating to the Portal the request has originated from.