Welcome to MSDN Blogs Sign in | Join | Help

SharePoint Visifire Charting with Custom List Data

imageVisifire is a set of data visualization components powered by Microsoft Silverlight which let you create and embed visually stunning animated Silverlight Charts within minutes.

Because it is a pre-compiled Silverlight control, the Visifire component and JavaScript library can be easily uploaded to a SharePoint document library and then used on a SharePoint page through a standard Content Editor Web Part. In fact, it doesn’t take long to imagine a scenario when a user might want to create an entire dashboard using these charts (see the bottom of this article for an example).

The purpose of this article is to describe the process for creating a dashboard of Visifire charts and to present a sample JavaScript library for accessing and working with both SharePoint list data and the creation of the markup required to render the Visifire charts.

For the purposes of this article I will be using a single SharePoint list containing proposed project data containing fields such as project cost, project benefit, and project type. Creating a Visifire chart from SharePoint list data can seem difficult at first but becomes much easier when using JQuery to access and massage the data. I will cover the following steps:

  1. Set up the web part page with the correct javascript references and placeholders for the charts.
  2. Retrieving SharePoint List Data using JQuery
  3. Summarizing the data with JavaScript and JQuery
  4. Building the XML for the Visifire charts.
Listing 1: Content Editor Web Part Code
<script type="text/javascript" 
src="Visifire2.js"></script>

<script type="text/javascript" src="My.Dashboard.Chart.js">
</script>


<h3>Visifire Chart</h3>
<table>
  <tr>
    <td><div id="VisifireChart1"></div></td>
    <td><div id="VisifireChart2"></div></td>
  </tr>
  <tr>
    <td><div id="VisifireChart3"></div></td>
    <td><div id="VisifireChart4"></div></td>
  </tr>
  <tr>
    <td><div id="VisifireChart5"></div></td>
    <td><div id="VisifireChart6"></div></td>
  </tr>
</table>

Setting Up

To get set up for this solution, create a new document library called Dashboards (you can name it whatever you want).

  1. Download the Visifire control, extract the contents and upload the Visifire2.js and SL.Visifire.Charts.xap files to your document library.
  2. Create a blank text file called My.Dashboard.Charts.js and upload this file to your document library.
  3. Create a Web Part Page in your document library (I selected the Full Page, Vertical layout).
  4. Add a Content Editor Web Part to the page and open the source editor. Add the code from Listing 1 into the source editor. Note: Each of the <div /> tags is for a separate chart to be explained later.
  5. Exit edit mode. You should now have a blank page.
  6. Next, we need the ID’s of the custom list and the view. Navigate to your list and change to the view containing the fields needed to be displayed in your dashboard. If a view doesn’t yet exist that contains all the fields you need, create a new view. On the View menu, select ‘Modify this View’. Copy the URL to the clipboard and go to the URL Decoder page. Paste in your URL and click Decode. Save the List ID and the View ID for later:
    List={2A09E4CF-DEC3-4AC7-B846-E0C9E9170001}&View={4EFB1CFC-C60B-4D2B-8266-37A1D12C98C1}&Source=http%…

Retrieving SharePoint List Data using JQuery

I my case I am using a single list and summarizing it in different ways to generate multiple charts on a single page. The first step is to ensure the JQuery library is referenced in the page. Right-click on the page and ensure that the following is included somewhere in the page:

<script type="text/javascript" src="/_layouts/jquery-1.3.2.min.js"></script>

If JQuery is not referenced you’ll need to add it to your Content Editor Web Part.

The next step is to use JQuery to retrieve your SharePoint list data. I followed the steps from Jan Tielens blog on querying SharePoint list items with JQuery to get started. Then I started building up a simple JavaScript library for my page to use. Open the My.Dashboard.Chart.js file in your favorite text editor and add the following code:

    $(document).ready(function() {
        var soapEnv =
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                <soapenv:Body> \
                     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        <listName>{ Paste Your List ID Here }</listName> \
                        <viewName>{ Paste Your View ID Here }</viewName> \
                        <viewFields /> \
                    </GetListItems> \
                </soapenv:Body> \
            </soapenv:Envelope>";

        $.ajax({
            url: "http://[ add your site URL here ]/_vti_bin/lists.asmx",
            type: "POST",
            dataType: "xml",
            data: soapEnv,
            complete: processResult,
            contentType: "text/xml; charset=\"utf-8\""
        });
    });

The code above will execute when the page loads and will submit a SOAP request to the SharePoint lists web service. When the web service call completes, the processRequest method will be called.

Let’s add a stub method for processRequest just so you can see what is going on behind the scenes and so we can see the names of our fields when they are returned from the web service:

    function processResult(xData, status) {
        var listData = $(xData.responseXML);
        var dataPoints = '';
        var chartXmlString = '';
        }

Save your .js file back to the document library and then open your web part page. Fire up Fiddler and then refresh the page. You should see an entry with a Status of 200 and a URL containing /_vit_bin/Lists.asmx:

image

Double-click on the row and the select the XML tab in the bottom-right pane of the window. Here you can see the actual XML returned by the web service. Take a note of the field names you need to use for your charts. Most of the field names will begin with “ows_”. We’ll use these field names to summarize our data. Before we can do that however, we need a couple of JavaScript functions to summarize the data for us and generate the XML required by Visifire to render the charts.

    function renderChart(xaml, divId) {
        var vChart = new Visifire2("SL.Visifire.Charts.xap", 450, 350 );
        vChart.setDataXml(xaml);
        vChart.render(divId);
    }

The renderChart() method takes in the xml generated below and uses the render() method from Visifire to create a chart in the specified <div/> tag.

    function getGroupedDataPoints(listData, groupByField, groupType, valueField, insertTotal) {
      var valueArray = new Array();
      var keyArray = new Array();
     
      $(listData).find("z\\:row").each(function() {
        var myKey = $(this).attr(groupByField);
        if (jQuery.inArray(myKey, keyArray) == -1) {
          keyArray[keyArray.length] = myKey;
          valueArray[keyArray.length] = 0;
        }
         
        switch(groupType) {
          case 'Count':
            if (valueArray[jQuery.inArray(myKey, keyArray)] == null || isNaN(valueArray[jQuery.inArray(myKey, keyArray)])) {
              valueArray[jQuery.inArray(myKey, keyArray)] = 0;
            }
            valueArray[jQuery.inArray(myKey, keyArray)]++;
            break;
          case 'Sum':
            var val = $(this).attr(valueField);
            if (val != null && !isNaN(val)) {
              if (valueArray[jQuery.inArray(myKey, keyArray)] == null) { valueArray[jQuery.inArray(myKey, keyArray)] = 0; }
              valueArray[jQuery.inArray(myKey, keyArray)]+= parseFloat(val);
            }
            break;
          default:
            break;
        }
      });
     
      var dataPoints = "";
      var totalValue = 0;
      for(var i=0; i<keyArray.length; i++) {
        var label = keyArray[i].replace("&", "&amp;");
        dataPoints += '<vc:DataPoint AxisXLabel="' + label + '" YValue="' + valueArray[i] + '" />';
        totalValue += valueArray[i];
      } 
     
      if (insertTotal) {
        dataPoints = '<vc:DataPoint AxisXLabel="Total" YValue="' + totalValue + '" />' + dataPoints;
      }
     
     
      return dataPoints;  
    }

The getGroupedDataPoints() function performs the summarizing of the data when given a field to group by, the type of grouping to perform (either 'Sum' or 'Count') and whether or not to include an additional total column in the data. The function returns a set of VisiFire DataPoint xml elements that will be used to generate the chart.

    function getXaml(title, chartType, dataPoints) {
      var chartXmlString = ''
      +'<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts" Width="450" Height="350" BorderThickness="1" BorderBrush="Silver" Theme="Theme1" Watermark="False" >'
          +'<vc:Chart.Titles>'
              +'<vc:Title Text="' + title + '" FontSize="16" FontFamily="Trebuchet MS" FontWeight="Bold" />'
          +'</vc:Chart.Titles>'
          +'<vc:Chart.AxesY>'
              +'<vc:Axis AxisType="Primary" />'
          +'</vc:Chart.AxesY>'
          +'<vc:Chart.Series>'
              +'<vc:DataSeries RenderAs="' + chartType + '" AxisYType="Primary" >'
                  +'<vc:DataSeries.DataPoints>'
                      + dataPoints
                  +'</vc:DataSeries.DataPoints>'
              +'</vc:DataSeries>'
          +'</vc:Chart.Series>'
      +'</vc:Chart>';    
        return chartXmlString;
    }

The getXaml method generates the XML required to complete the chart. Simply provide a chart title, chart type and the set of data points and the rest is done for you. The getXaml function with allow you to create charts like the following:

image

image

Now that the functions are all in place for formatting our web service data into Visifire charts, we need to call the methods to make all the magic happen. Let's go back to our processResult() method:

    function processResult(xData, status) {
        var listData = $(xData.responseXML);
        var dataPoints = '';
        var chartXmlString = '';


        dataPoints = getGroupedDataPoints(listData, 'ows_Category', 'Sum', 'ows_Expense', false);
        chartXmlString = getXaml('Planned Expense by Category', 'Pie', dataPoints);
        renderChart(chartXmlString, 'VisifireChart1');

        dataPoints = getGroupedDataPoints(listData, 'ows_ProjectType', 'Count', null, false);
        chartXmlString = getXaml('Project Type', 'Pie', dataPoints);
        renderChart(chartXmlString, 'VisifireChart3');

        var dataPointsStandard = getGroupedDataPoints(listData, 'ows_Status', 'Sum', 'ows_Expense', true);
        var dataPointsType2 = getGroupedDataPoints(listData, 'ows_Status', 'Sum', 'ows_ExpenseType2', true);
        chartXmlString = getTwoSeriesXaml('Standard vs. Type 2 Projects', 'Standard', dataPointsStandard, 'Type 2', dataPointsType2);
        renderChart(chartXmlString, 'VisifireChart5');

    }

Ok, so if you are actually still following along with me—congratulations! You get an added bonus. You’ll notice a new method in there, getTwoSeriesXaml(). Visifire supports graphing of multiple series. Here’s the code for the getTwoSeriesXaml function:

    function getTwoSeriesXaml(title, seriesATitle, seriesADataPoints, seriesBTitle, seriesBDataPoints) {
      var chartXmlString = ""
      +'<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts" Width="450" Height="350" BorderThickness="1" BorderBrush="Silver" Theme="Theme1" Watermark="False" >'
          +'<vc:Chart.Titles>'
              +'<vc:Title Text="' + title + '" FontSize="16" FontFamily="Trebuchet MS" FontWeight="Bold" />'
          +'</vc:Chart.Titles>'
          +'<vc:Chart.AxesY>'
              +'<vc:Axis AxisType="Primary" Prefix="$" />'
          +'</vc:Chart.AxesY>'
          +'<vc:Chart.Series>'
              +'<vc:DataSeries RenderAs="Column" AxisYType="Primary" LegendText="' + seriesATitle + '" LabelEnabled="True">'
                  +'<vc:DataSeries.DataPoints>'
                      + seriesADataPoints
                  +'</vc:DataSeries.DataPoints>'
              +'</vc:DataSeries>'
              +'<vc:DataSeries RenderAs="Column" AxisYType="Primary" LegendText="' + seriesBTitle + '" LabelEnabled="True">'
                  +'<vc:DataSeries.DataPoints>'
                      + seriesBDataPoints
                  +'</vc:DataSeries.DataPoints>'
              +'</vc:DataSeries>'
          +'</vc:Chart.Series>'
      +'</vc:Chart>';    
        return chartXmlString;
    }

The final step is to save your JavaScript file back to SharePoint and refresh your page (be sure to use Ctrl+F5 to ensure you have the latest version of all your files). Here’s my final dashboard; click for the full size version. I’d love to see what you can come up with.

image

Posted by dougperkes | 5 Comments
Filed under:

Quick Tip: Upgrading a bunch of SharePoint .wsp's at one time

I had to upgrade all of the solution packages on my dev virtual machine and thought I would share a simple method to do the upgrade.

The first step is to make sure the path to the STSADM tool is in your environment path. Once done you can easily call STSADM from any command window or from within Powershell.

Open Powershell and Set-Location (shortcut is CD) to the directory with all of your .wsp’s. Then simply run the following Powershell command depending on your environment

Single Dev Machine:

get-childitem *.wsp | ForEach-Object { "Upgrading $($_.Name)"; stsadm.exe -o upgradesolution -name $_.Name -filename $_.Name –local -allowgacdeployment }

Multi-server farm environment:

get-childitem *.wsp | ForEach-Object { "Upgrading $($_.Name)"; stsadm.exe -o upgradesolution -name $_.Name -filename $_.Name –immediate –allowgacdeployment; stsadm –o execadmsvcjobs }

That’s it!

Posted by dougperkes | 0 Comments
Filed under:

SharePoint Search Results: Adding a link to the view properties page of a document

The out-of-the-box SharePoint search results web part doesn't provide a way to get to the View Properties page for items in a document library. The search results provide a direct link to the document which will open a document in default editor registered within windows.

In the screen shot below notice that both the hyperlinked title (shown in the tool tip) and the url displayed both link to the actual document and not to the View Details page.

clip_image001

The goal is to generate a hyperlink that will allow the user to navigate to the properties display form for documents.

clip_image002

Clicking "View Properties" will take the user to the following page:

clip_image003

Step 1: Add a managed property for the integer ID of list items.

In order for the solution to work properly we must first create a Metadata Property Mapping for the ID of list items. SharePoint out-of-the-box does not include these identifiers in its search index.

  1. Open the SSP
  2. Click Search Administration
  3. Under Queries and Results, click Metadata properties.
  4. Click Crawled Properties:
    clip_image004
  5. Search for ows_id. Then edit the crawled property.
    clip_image005
  6. Select the checkbox at the bottom of the page to include the property in the search index:
    clip_image006
  7. Return to the Metadata Property Mappings screen by clicking on Metadata properties on the left side of the page.
  8. Click New Managed Property and set the values as follows:
    • Property Name: ListItemID
    • Description: Numeric identifier of SharePoint List Items (i.e. documents, pictures, and custom lists).
    • Type: Integer
    • Add ows_id as a crawled property.
  9. Run a full crawl of the content source

Step 2: Add the Metadata property to the search results web part

Now that the metadata property has been added to SharePoint we now need to make the property available to the search results web part. In my example I've added the search and search results web parts to a web part page but the same steps apply if you are editing the search results web part in the search center.

  1. Open your search results page in edit mode.
  2. Locate the search results web part and click Modify Shared Web Part
    clip_image007
  3. In the Results Query Options section, open the editor for the Selected Columns property:
    clip_image008
  4. Add a reference to the ListItemId property as follows:
    <Column Name="ListItemID" /> 

    At this point the custom Metadata property we created will now be retrieved with every search query. The value will come back in the xml returned by the query servers and is just begging to be displayed somewhere on the page.

Step 3: Modify the XSL to display the hyperlink to the View Properties page.

The next step it to create a hyperlink to the View Properties page using not only the custom ListItemId Metadata property but also several other properties already available to us: url, sitename, and contentclass.

  1. Click the XSL Editor button in the web part properties panel.
  2. Copy the contents of the editor and paste into an XSL editor like Visual Studio .NET or any text editor.
  3. Near the bottom of the XSL file paste in the following template definition just prior to the </xsl:stylesheet> tag:
    <!-- A custom template to display a link to view the properties for a document -->
    <xsl:template name="DisplayViewPropertiesLink">
      <xsl:param name="itemUrl" />
      <xsl:param name="siteUrl" />
      <xsl:param name="listItemId" />
      <xsl:param name="contentclass" />
      <xsl:if test="$contentclass='STS_ListItem_DocumentLibrary'">
        <xsl:variable name="docLibLoc" select="substring-before(substring-after($itemUrl, concat($siteUrl, '/')), '/')" />
        <xsl:variable name="viewPropUrl" select="concat($siteUrl, '/', $docLibLoc, '/Forms/DispForm.aspx?id=', $listItemId)" />
        - <a href="{$viewPropUrl}">View Properties</a>
      </xsl:if>
    </xsl:template>

    This template first checks to see if the item is part of a document library. If so, it parses out the document library name and then builds up a URL pointing to the View Properties page and outputs a hyperlink to the page.

    Important note: This link is hard-coded to point to the out-of-the-box View Properties page. If document libraries have custom View Properties pages, this solution will need to be modified.

  4. The last step is to include a call to the new StyleSheet template. In my example, I've placed the call just after the rendering of the modified date:
    <xsl:call-template name="DisplayString">
      <xsl:with-param name="str" select="write" />
    </xsl:call-template>
    
    <xsl:call-template name="DisplayViewPropertiesLink">
      <xsl:with-param name="itemUrl" select="url" />
      <xsl:with-param name="siteUrl" select="sitename" />
      <xsl:with-param name="listItemId" select="listitemid" />
      <xsl:with-param name="contentclass" select="contentclass" />
    </xsl:call-template>
    
    <xsl:call-template name="DisplayCollapsingStatusLink">

    Step 4: Deploy and test

    Now that the XSL StyleSheet has been modified the last step is to copy the XSL back into the web part property and save your changes. Submit a simple search for documents and verify the View Properties link appears after the date.

    clip_image009

Special thanks to Jeremy Jameson for providing the idea and starter code for this solution.

Posted by dougperkes | 5 Comments
Filed under:

TFS Build Guide for SharePoint Projects

Important Note: This guide is meant to be a general overview and by no means is exhaustive. For detailed information about Site Definitions, Features and Solution Packages see the Windows SharePoint Services SDK (http://msdn2.microsoft.com/en-us/library/ms441339.aspx) and Chapter 9 of Ted Pattison’s book “Inside Windows SharePoint Services 3.0”.

Understanding SharePoint Solutions and Deployments

Site Definitions

A site definition is the top-level component in WSS that aggregates smaller, more modular definitions to create a complete site template that can be used to provision sites. For example, a site definition usually includes a custom page template for the site’s home page and can additionally reference external features to add support for other types of site-specific elements such as custom lists, secondary pages, and web parts. Creating custom site definitions enables you to develop site templates for creating new sits that act as prepackages business solutions.

Features

Features are the preferred packaging mechanism for SharePoint components. Features are easy to activate within existing sites and also have object and event models that are very powerful. A feature can be activated with a WSS site in five ways:

  • Site Settings Web Pages
  • STSADM command line
  • Site definition references
  • Feature activation dependencies
  • Feature stapling

Deployment Using Solution Packages

From Chapter 9 of Inside Windows SharePoint Services 3.0 by Ted Pattison:

Because SharePoint solutions are deployed on WSS or MOSS installations that range in size from a single standalone Web server to large enterprise server farms, there needs to be a mechanism to deploy them as a single unit. By deploying a single unit, we can have a supported, testable, and repeatable deployment mechanism. The deployment mechanism that SharePoint uses is the solution package.

Solution packages are critical components for deployment in enterprise scenarios and use the same Visual Studio project formats that you work with on your development box. For all deployments outside of your development environment, you want to use WSS solution packages. Solution packages enable your system administrators to create scriptable installs, an important requirement for Contoso.

It is also important to test solution package deployments in a controlled WSS environment (that is not the same as your development environment), such as a clean Virtual PC image, to test installation of features, site definitions, assemblies, and configuration changes using solution packages. Solution packages are generally language neutral without localized resources, supplemented by solution language packs that contain the language-specific resources.

clip_image002A solution package is a compressed .cab file with a .wsp extension containing the components to be deployed on a target web server. A solution package also contains additional metadata that enable the WSS runtime to uncompress the .cab file and install its components. In the case of server farm installations, WSS is able to automate pushing the solution package file out to each Web server in the farm.

Solution packages are deployed by using two steps. The first step is installation, in which WSs copies the .wsp file to the configuration database. The second step is the actual deployment, in which WSs creates a timer job that is processed by all front-end Web servers in the server farm. This greatly simplifies the installation across farm servers and ensures a consistent deployment.

The .wsp file for a solution package can be build using the MAKECAB operating system utility by reading the definition from a .ddf (Diamond Directive File) file. The .ddf file defines the output structure of the .wsp file be referencing each file in its source location and its destination location in the .wsp file. This is one of the more tedious aspects of WSS development because you will likely need to create and maintain the .ddf file by hand.

The metadata for a solution package is maintained in a file named manifest.xml that must be added to the root of the .wsp file. It is the manifest.xml file that tells the WSS runtime which template files to copy into the WSS sytem directories. The manifest.xml file can also instruct WSS to install features and assembly DLL as well as add entries to one or more web.config files for SafeControl entries and code access security settings.

An article on MSDN titled “Solution Deployment with SharePoint 2007” (http://msdn.microsoft.com/msdnmag/issues/07/08/officespace/default.aspx) provides a sample project and walks through creating a solution package. Figure 1 shows a sample project in Visual Studio.NET. Notice the two files in the Solution directory, Cab.ddf and manifest.xml. The file Cab.ddf, see Listing 1, defines which files are placed into the .cab file and their locations. The manifest.xml file instructs the WSS runtime how to read the files in the cab and how to create the files on the file system.

Listing 1: Sample DDF

   1:  .OPTION EXPLICIT ; Generate errors 
   2:  .Set CabinetNameTemplate=OfficeSpaceFeature.wsp
   3:  .Set DiskDirectory1=Package
   4:  .Set Cabinet=on
   5:  .Set MaxDiskSize=0
   6:  .Set CompressionType=MSZIP;
   7:  .Set DiskDirectoryTemplate=CDROM;
   8:   
   9:  Solution\manifest.xml manifest.xml
  10:   
  11:  TEMPLATE\FEATURES\OfficeSpaceFeature\feature.xml OfficeSpaceFeature\feature.xml
  12:  TEMPLATE\FEATURES\OfficeSpaceFeature\elements.xml OfficeSpaceFeature\elements.xml
  13:  TEMPLATE\FEATURES\OfficeSpaceFeature\LetterTemplate.docx OfficeSpaceFeature\LetterTemplate.docx
  14:  TEMPLATE\LAYOUTS\OfficeSpace\LetterGenerator.aspx LAYOUTS\OfficeSpace\LetterGenerator.aspx
  15:  TEMPLATE\IMAGES\OfficeSpace\PithHelmet.gif IMAGES\OfficeSpace\PithHelmet.gif
  16:   
  17:  bin\Debug\OfficeSpaceFeature.dll OfficeSpaceFeature.dll
  18:   
  19:  ; end of DDF file

Listing 2: Sample manifest.xml

   1:  <Solution 
   2:  SolutionId="{848ACD1F-DEBC-4078-B21D-56CECE3F499B}" 
   3:  xmlns="http://schemas.microsoft.com/sharepoint/">
   4:  <FeatureManifests>
   5:    <FeatureManifest Location="OfficeSpaceFeature\feature.xml" />
   6:  </FeatureManifests>
   7:  <TemplateFiles>
   8:   <TemplateFile Location="LAYOUTS\OfficeSpace\LetterGenerator.aspx" />
   9:   <TemplateFile Location="IMAGES\OfficeSpace\PithHelmet.gif"/>
  10:  </TemplateFiles>
  11:  <Assemblies>
  12:   <Assembly DeploymentTarget="GlobalAssemblyCache" Location="OfficeSpaceFeature.dll">
  13:  <!-- this project does not contain any web controls, but the SafeControl entry is here to demonstrate how to install a feature package when it contains elements that have to make modifications to the web.config file.
  14:  -->
  15:    <SafeControls>
  16:     <SafeControl Assembly="OfficeSpaceFeature, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31f10bb2f694edab" Namespace="OfficeSpaceFeature" TypeName="*" Safe="True" />
  17:     </SafeControls>
  18:    </Assembly>
  19:   </Assemblies>
  20:  </Solution>
  21:   

Configure Visual Studio to compile the .wsp

Visual Studio can be configured to compile the feature files into a .wsp whenever the project is built. The following steps show how to configure this.

Prerequisite
You need to download and install The MSBuild Community Tasks Project from http://msbuildtasks.tigris.org/. This includes a set of custom tasks for use in our project.

Create the SharePointFeaturePackage.targets file

MSBuild can be extended by creating .targets files and configuring the project to call specific targets in the file. In this case we will create a file names SharePointFeaturePackage.targets and configure the file the compile the project into a .wsp.

Start out by creating a new file in the solution by right-clicking on the solution name and select Add „ New Item.:

clip_image005

Select XML File and name it SharePointFeaturePackage.targets:

clip_image007

Create an XML element named Project as follows:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

Because we plan on using the MSBuild Community Tasks in our solution, let’s add a reference to them in our .targets file:

<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

The goal of this .targets file is to use the MakeCab utility to compress all the feature files into a single .wsp file. In order to do this we need to create a property that tells MSBuild where the utility resides in the file system. We also need to pass the output directory into MakeCab so that it knows where to place the compressed file. To accomplish this, we need to create an MSBuild PropertyGroup element and specify these properties:

<PropertyGroup>
  <MAKECAB>"C:\Windows\System32\makecab.exe"</MAKECAB>
  <DiskDirectory1>"$(OutDir)"</DiskDirectory1>
  <DiskDirectory1 Condition="HasTrailingSlash($(OutDir))">"$(OutDir)."</DiskDirectory1>
</PropertyGroup>

Notice two important items about the DiskDirectory1 property. First, we set the default value of DiskDirectory1 to an MSBuild variable called OutDir. On a developer virtual machine, the OutDir variable will be either bin\debug\ or bin\release\. On the build server, OutDir will be the full path to the compilation directory, i.e. D:\DATA\TeamBuilds\Contoso.ACS.HS - Contoso Video Systems\Sample\Binaries\Debug\. Second, we conditionally add a trailing dot if the OutDir ends with a slash. This is required because MakeCab can’t handle the path ending with a slash.

With the properties in place, we are ready to create the MSBuild target for the .wsp creation. MakeCab makes uses the .ddf file created in your project, so make sure you have that in place before trying to compile the project. To create the target, create a new xml element called Target:

<Target Name="SharePointFeaturePackage">

We next need to consider the path to any assemblies referenced by the DDF. The MakeCab utility allows us to pass parameters, however, I have been unable to correctly pass paths with spaces in them to MakeCab. To get around this, we need to modify the Cab.ddf file in order to set the correct path to the assemblies. We will use a custom MSBuild task from the MSBuild Community Tasks project to accomplish this. First make a copy of the original .ddf file:

<Copy SourceFiles="DeploymentFiles\Cab.ddf" DestinationFiles="DeploymentFiles\WorkingCab.ddf" />

Next, let’s make sure our .ddf file has some tokens in it that we can replace with the correct path. Replace any hard-coded paths to your assembly (i.e. bin\debug\MyAssembly.dll MyAssembly.dll) with the following:

#AssemblyFilePath# #AssemblyFileName# 

Now, back to our .targets file. After the Copy task we can use the custom FileUpdate task to replace the tokens with real values:

<FileUpdate Files="DeploymentFiles\WorkingCab.ddf"
    Regex="#AssemblyFilePath# #AssemblyFileName#"
    ReplacementText="%22$(OutDir)$(AssemblyName).dll%22 %22$(AssemblyName).dll%22" />

Now, within the target we can use the Exec task to call the MakeCab utility:

<Exec Command="$(MAKECAB) /F DeploymentFiles\WorkingCab.ddf /D CabinetNameTemplate=$(MSBuildProjectName).wsp /D DiskDirectory1=$(DiskDirectory1)" />

Additionally, I like to call the Exec task a second time when compiling in debug mode to create another version of the package with a .cab extension. This makes it much easier to look inside the file to see exactly what was added and the folder structure within it:

<Exec Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU'"
    Command="$(MAKECAB) /F DeploymentFiles\WorkingCab.ddf /D CabinetNameTemplate=$(MSBuildProjectName).cab /D DiskDirectory1=$(DiskDirectory1)" />
Configure the Visual Studio project to call the SharePointFeaturePackage target

We now need to configure our Visual Studio project to call the SharePointFeaturePackage target after the project has been compiled. Alternatively, if the project does not contain any class files that need to be compiled, we can configure the project to call our custom target instead of compiling. To do this we have to edit the .csproj file and add import our .targets file. Since a .csproj file is just an MSBuild file, this process should look familiar.

Right-click the project name in Solution Explorer and select “Unload Project”:

clip_image009

Once the project has unloaded, right-click the project name in Solution Explorer and select “Edit [your project name].csproj”

clip_image011

Look for the <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> element and add the following right after it:

<Import Project="SharePointFeaturePackage.targets" />

You may need to alter the path to the .targets file based on where on the file system you created the file.

Call the SharePointFeaturePackage target

If your project compiles C# code into an assembly, add the following into the AfterBuild target:

<CallTarget Targets="SharePointFeaturePackage" />

If your project does not compile any code, change the DefaultTargets attribute of the Project elements to SharePointFeaturePackage:

<Project ToolsVersion="3.0" 
    DefaultTargets="SharePointFeaturePackage" 
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

Build your project and verify that both a .wsp and .cab file appear in the bin\debug or bin\release directory depending on the solution configuration selected.

Note: As additional projects are added, they can all use the same SharePointFeaturePackage.targets file. You simply need to change the Import element to supply the correct path to the .targets file. For example:

<Import Project="..\OfficeSpaceFeatureCS\SharePointFeaturePackage.targets" />
Posted by dougperkes | 2 Comments
Filed under: ,
 
Page view tracker