View from inside the viper

Using the Subversion Revision Number in NAnt

  August 20, 2011 18:37
by Mark

We have recently updated our NAnt build script used to build Cart Viper to include the revision number from Subversion. So now when we build the project it will update all the build numbers of the DLLs to have a revision number equal to the revision number from Subversion.

DLL build number as set using NAnt script

In the example above the major, minor and build numbers come from a property we define in the NAnt script, so we just need to change this to increment all the DLLs to the new number.

The revision number of 2250 is the revision number from Subversion, so in the future we now that this DLL was compiled using revision 2250 of our code base. This is automatically read from Subversion using the script below.

Read the Revision Number from Subversion

Note we use TortoiseSVN so we are using a binary from that program to get the revision number.

First we need to commit a special text file to the Subversion repo, this file contains a token which when we output the file from the repo will be replaced with the latest SVN revision number.
The file should just contain the token

$WCREV$

Contents of the rev.txt

Make sure you commit the file into the repo, my file is call rev.txt

For our build process we have a sub folder off the root of the project called build which contains the NAnt script and the revision file.

In the NAnt build script we have the init target which handles updating the build number.

<target name="init">
    <!-- basic verison number to set for the DLLs -->
    <property name="shortVersion" value="1.4.1"/>
    
    <property name="build.version.revision" value="0" />

	<exec program="subwcrev" basedir="C:\Program Files\TortoiseSVN\bin" 
		commandline='./ rev.txt revvalue.txt' failonerror="false"/>

The part that does the work is the exec element, this calls the subwcrev command line tool to output the rev.txt file to a file called revvalue.txt. This will have the effect of replacing the token with the reversion number.

Now we need to read the contents of the file into a property in NAnt.

    <loadfile file="./revvalue.txt" property="build.version.revision"/> 

Updating the AssemblyInfo.cs Files in NAnt

The final thing we need to do is take the version numbers we have and replace these in all the AssemblyInfo.cs files in our project.
To do this we need to use some custom code in the script. Remember we have our build file in a sub folder off the root of the main project file. So we need to move up one level, you may need to change this in relation to the location of the NAnt build file and the project root (see line 12).

<!-- run some custom code to find all the files then 
         update the rev number to the one we've just read from 
         subversion --> 
     <script language="C#"> 
          <references> 
              <include name="System.dll" /> 
          </references> 
          <imports> 
              <import namespace="System.Text.RegularExpressions" /> 
          </imports> 
          <code> 
            <![CDATA[ 
              public static void ScriptMain(Project project) { 
                  DirectoryInfo baseDirectory =  new DirectoryInfo(project.BaseDirectory); 
                  DirectoryInfo rootDirectory = baseDirectory.Parent; 
                  
                  string buildNumber = project.Properties["shortVersion"].Replace("\r\n", ""); 
                  string revisionBuild = project.Properties["build.version.revision"].Replace("\r\n", ""); 
                  StringBuilder buffer = new StringBuilder(); 
                  
                  var files = rootDirectory.EnumerateFiles("AssemblyInfo.cs", SearchOption.AllDirectories); 
                  
                  foreach(FileInfo fi in files){ 
                    string fileContents; 
                  
                    using(FileStream stream = fi.Open(FileMode.Open, FileAccess.ReadWrite )){ 
                      using(StreamReader reader = new StreamReader(stream)){ 
                        fileContents = reader.ReadToEnd(); 
                        
                        //set the assemblyVersion 
                        string assemVersion = Regex.Match(fileContents, @"\[assembly: AssemblyVersion.*").Value; 
                        
                        if(!string.IsNullOrEmpty(assemVersion)){ 
                          buffer.Append(assemVersion.Substring(0, assemVersion.IndexOf('"') + 1)); 
                          buffer.Append(buildNumber); 
                          buffer.Append("."); 
                          buffer.Append(revisionBuild); 
                          buffer.Append("\")]"); 
                          
                          fileContents = fileContents.Replace(assemVersion, buffer.ToString()); 
                          buffer.Clear(); 
                        } 
                        
                        //set the AssemblyFileVersion 
                        string assemFileVersion = Regex.Match(fileContents, @"\[assembly: AssemblyFileVersion.*").Value; 
                        if(!string.IsNullOrEmpty(assemFileVersion)){ 
                          buffer.Append(assemFileVersion.Substring(0, assemFileVersion.IndexOf('"') + 1)); 
                          buffer.Append(buildNumber); 
                          buffer.Append("."); 
                          buffer.Append(revisionBuild); 
                          buffer.Append("\")]"); 
                          fileContents = fileContents.Replace(assemFileVersion, buffer.ToString()); 
                          buffer.Clear(); 
                        } 
                      } 
                    } 
                    
                    using(FileStream stream = fi.Open(FileMode.Truncate, FileAccess.Write)){ 
                      using(StreamWriter writer = new StreamWriter(stream)){ 
                        writer.Write(fileContents); 
                      } 
                    } 
                    
              } 
            } 
            ]]> 
          </code> 
      </script> 
</target>

Now when you build the script the build numbers will be updated automatically.



Getting PortalSettings Instance outside HttpContext in DotNetNuke

  August 13, 2010 15:59
by Nigel

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:     }
  12:  
  13:     string cookieValue;
  14:  
  15:     if (SymmetricHelper.CanSafelyEncrypt)
  16:         cookieValue = SymmetricHelper.Encrypt(this.PortalId.ToString());
  17:     else
  18:         cookieValue = PortalId.ToString();
  19:  
  20:     cookie["PortalId"] = cookieValue;
  21:  
  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();
   5:  
   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:         }
  28:  
  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.