in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

Tech MOSS Team

September 2007 - Posts

  • SharePoint Programmatically: Managing Solutions

    Managing SharePoint 2007 Solutions programmatically isn’t a daily-matter: some of you might even participate on quite a few projects without even coming close to it. As I have worked with it recently a bit I would like to share my findings with you.

    I assume you know what the SharePoint solutions (.wsp packages) are and how do they work. What I’d like to focus on is working with the Solutions programmatically (through the SharePoint Object Model): adding new solutions to the Solution Store, deploying Solutions to Web Applications, retracting and removing them.

    First of all let’s take a look at accessing the local Solution Store. For simplicity I will use the local farm. You will probably use the same in your code:

    SPFarm.Local.Solutions

    Solutions contain all available solutions on the chosen farm.

    You can add a Solution to the Solution Store by simply passing the local path to the .wsp file:

    string solutionPath = @"C:\somesolution.wsp";
    SPSolution solution = SPFarm.Local.Solutions.Add(solutionPath);

    Adding a solution to the Solution Store will not overwrite the earlier versions. So if you would like to replace your old .wsp file with a new one, you will need to retract (if deployed) and remove the old one. I will show you how to do it later on.

    Adding a solution doesn’t really triggers anything: it just makes the solution available – you still can’t use it though. You need to deploy the solution to a Web Application to actually use it. In the following example I first obtain the Content Web Service using the ContentService property. It allows me to obtain the Web Applications used by SharePoint. Then I select some Web Application where I would like to deploy the solution to.

    Collection<SPWebApplication> selectedWebApps = new Collection<SPWebApplication>();
    SPWebService ws = SPWebService.ContentService;
    webApps = ws.WebApplications;
    selectedWebApps.Add(webApps["My Web App"]);
    selectedWebApps.Add(webApps["SharePoint - 80"]);
    solution.Deploy(DateTime.Now, true, selectedWebApps, true);

    You need to set the second parameter to true only if your solution needs to deploy some assemblies to the GAC. Otherwise it can be set to false: the assemblies will be deployed to the bin directory of the selected Web Applications.

    Your solution has just been added to the local Solution Store and deployed to the selected Web Applications. Now I would like to show you how to retract your solution from the Web Applications where it has been deployed to.

    First of all you need to get your solution as an instance of the SPSolution object. Personally I use the custom GetSolutionByName method which allows me to obtain the required solution as object by passing the solution’s name.

    private SPSolution GetSolutionByName(string solutionName)
    {
           SPSolutionCollection sc = SPFarm.Local.Solutions;
           foreach (SPSolution s in sc)
           {
                  if (s.Name.ToLower() == solutionName.ToLower())
                         return s;
           }

           return null;
    }

    To retract the solution:

    SPSolution solution = GetSolutionByName(solutionName);
    if (solution != null)
    {
           if (solution.DeployedWebApplications != null)
           {
                  solution.RetractLocal(solution.DeployedWebApplications);
           }

           ExecuteJobDefinitions();
           SPFarm.Local.Solutions.Remove(solution.Id);
    }

    First of all I use the DeployedWebApplications property of SPSolution to check whether the solution has been deployed at all. If so (the collection is not null), I use the same property to pass the collection of Web Applications where I would like to retract the solution from. As retracting a Solution creates a timer job on the farm you might want to speed it up a bit – especially if you retract the solution right before adding the new version of it:

    private void ExecuteJobDefinitions()
    {
           if (SPFarm.Local.TimerService.JobDefinitions != null)
           {
                  foreach (SPJobDefinition job in SPFarm.Local.TimerService.JobDefinitions)
                  {
                         try
                         {
                               job.Execute(SPServer.Local.Id);
                         }
                         catch { }
                  }

           }
    }

    The last part is to remove the solution from the Solution store by passing its ID to the Remove method. Retracting and removing a Solution prevent you from getting an error while installing a Solution that already exists.

    So, where could you make use of all this? The above might come handy while deploying your solutions on your customers' machine - especially if you have created a product-like solution and you would like to ship it with a user-friendly setup. Another usage scenario could be using features for deploying solutions and scaling the deployment.

  • Meet us at the TechEd Europe 2007 in Barcelona

    Yes! Waldek and I (John) are going to the TechEd Europe in Barcelona from 5-9 november 2007. It is too bad that only 2 persons of our company can go there... but Erik has been there in 2006.

    Ofcourse we have special interest for the MOSS sessions but other interesting topics are: ASP.NET, AJAX, Silverlight, WPF and RIA (Rich Internet Applications)
    We promise to keep you posted during the TechEd about our experiences.

    Please drop a comment if you also will be there and want to meet us in Barcelona. We will buy you a beer Beer
    We always like to discuss: accessible MOSS websites, easy deployment of custom MOSS code, combining MOSS with Silverlight and/or AJAX, Vista gadgets... and much more

     See you in Barcelona!

     P.S. don't forget to download my TechEd Vista gadget

    Posted Sep 26 2007, 03:51 PM by john.bruin with no comments
    Filed under:
  • Using .NET Custom Attributes for release documentation

    During the last Dev Days in the Netherlands I’ve attended to Francesco Balena's presentation on .NET Reflection. It was definitely interesting and it has inspired me to take a closer look at it. After a while I came on the idea of using .NET Reflection for generating release documentation for .NET applications and assemblies. It would once and for all solve the problem of updating the release notes stored in a separate file and getting sure all the changes have been put there.

    I would like to show you the way I’ve put my idea into practice and how you could take it even further.

    My demo CustomAttributes solution consists of three projects:

    • CustomAttributes – definition of the custom attributes
    • CustomAttributesClient – a Windows Forms Application generating the release notes using the .NET reflection
    • MyAssembly – a dummy assembly that makes use of the custom attributes should resemble your assemblies

    Solution overview 

    First of all I’ve created the CustomAttributes project. I’ve started with creating two custom attributes: Change (for keeping changes) and Features (for marking extra functionality added to the assembly in the given release). Then I thought that it would be nice to store the bug fixes as well. As a bug fix is nothing more than a change I’ve decided to create a ChangeBase abstract class and make the Change and BugFix classes derive from it. For clarity I’ve stored each custom attribute in a separate class file.

    After defining the attributes I moved to setting the attributes. I’ve decided to store a date and a description of each change – no matter if it’s a Change or a BugFix. As we all use unique initials here at Imtech ICT Business Solutions I’ve added an optional Named Parameter to the ChangeBase class.

    public abstract class ChangeBase : Attribute
    {
           protected DateTime _Date;
           protected string _Change;

           private string _Initials;
           public string Initials
           {
                  get { return _Initials; }
                  set { _Initials = value.ToUpper(); }
           }

           public ChangeBase(string changeDate, string changeDescription)
           {
                  _Date = DateTime.Parse(changeDate);
                  _Change = changeDescription;
           }
    }

    Then I’ve moved on to defining the Change and BugFix attributes. Each custom attribute has to have the usage scope defined. As a change or a bug fix can apply almost to any part of a assembly I’ve decided to set the scope to AttributeTargets.All. Because I want to keep the history of the changes I’ve set the AllowMultiple attribute to true.

    Here are both the attributes:

    [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
    public class ChangeAttribute : ChangeBase
    {
           public DateTime Date
           {
                  get { return base._Date; }
           }
     
           public string Change
           {
                  get { return base._Change; }
           }
     
           public ChangeAttribute(string changeDate, string changeDescription)
                  : base(changeDate, changeDescription)
           {
           }
    }

    And the BugFix:

    [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
    public class BugFixAttribute : ChangeBase
    {
           public DateTime BugFixDate
           {
                  get { return base._Date; }
           }
     
           public string BugFixDescription
           {
                  get { return base._Change; }
           }
     
           public BugFixAttribute(string fixDate, string fixDescription)
                  : base(fixDate, fixDescription)
           {

           }
    }

    For clarity I’ve given new names to the date and description properties in the BugFix attribute. Then I’ve moved to the Feature attribute. First of all the attributes: I definitely wanted to store the name of each feature and the version of the release it came out with. Next to these I’ve set some optional space for extra description if needed. As for setting the attribute scope I’ve chosen for class, method and enumeration only: extra functionality is mostly a bigger chunk of code. Initially I’ve been using class and method only. Recently however I’ve faced expanding one of my assemblies with a quite important enumeration, so I’ve decided to extend the Feature attribute with the enumeration scope as well. So here is how the Feature attribute looks like:

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Enum, AllowMultiple = false)]
    public class FeatureAttribute : Attribute
    {
           private string _Name;
           public string Name
           {
                  get { return _Name; }
           }
     
           private Version _AddedInVersion;
           public Version AddedInVersion
           {
                  get { return _AddedInVersion; }
           }
     
           private string _Description;
           public string Description
           {
                  get { return _Description; }
                  set { _Description = value; }
           }

           public FeatureAttribute(string featureName, string addedInVersion)
           {
                  _Name = featureName;
                  _AddedInVersion = new Version(addedInVersion);
           }
    }

    At this moment we have defined three custom attributes we can use for storing the changes within our assemblies and using them for generating release documentation. Below you can see an exemplary assembly making actual use of the defined custom attributes:

    public class ClassFoo
    {
           public ClassFoo()
           {
     
           }
     
           [Feature("Sum of n-integers", "1.0")]
           public static int Sum(params int[] numbers)
           {
                  int sum = 0; 

                  foreach (int i in numbers)
                         sum += i;
     
                  return sum; 
          
    }
     
           [Feature("Sum of n-doubles", "1.1")]
           public static double Sum(params double[] numbers)
           {
                  double sum = 0.0;
     
                  foreach (double d in numbers)
                         sum += d;
     
                  return sum;
           }
     
           [Feature("Average of n-integers", "1.1")]
           public static double Avg(params int[] numbers)
           {
                  if (numbers.Length > 0)
                  { 
                       
    double avg = Sum(numbers) / numbers.Length;
     
                       
    return avg;
                  } 
                 
    else
                         return 0;
           }
     
           [Feature("Average of n-doubles", "1.1")]
           [Change("2007-09-16", "Exception if no numbers passed")]
           [BugFix("2007-09-17", "Diving by 0 check implemented")]
           public static double Avg(params double[] numbers)
           {
                  if (numbers.Length > 0)
                  {
                         double avg = Sum(numbers) / numbers.Length;
                         return avg;
                  }
                  else
                         throw new ArgumentException("You haven't passed any numbers to this method");

           }
    }

    The assembly doesn’t do anything spectacular actually: just a few simple methods. What I would like to focus on is the usage of the attributes.

    Let’s proceed with the last part of the solution: release notes generator. I’ve use for this purpose a simple Windows Forms Application with a button, a label (for displaying the file name), a Multiline TextBox for displaying the generated documentation and an OpenFileDialog for choosing the assembly: nothing spectacular so far. First of all I initiate the OpenFileDialog and let the user pick an assembly:

    if (openFileDialog.ShowDialog() == DialogResult.OK)
    {
           OpenFileLabel.Text = openFileDialog.FileName;

           GenerateReleaseNotes();
    }

    Then we move to the core:

    private void GenerateReleaseNotes()
    {
           Assembly a = Assembly.LoadFile(openFileDialog.FileName);
     
           foreach (Module m in a.GetModules())
           {
                  //InfoTextBox.Text += m.Name + "\r\n";
                  foreach (Type t in m.GetTypes())
                  {
                         //InfoTextBox.Text += t.Name + "\r\n";
                         foreach (MethodInfo mi in t.GetMethods())
                         {
                               if (mi.GetCustomAttributes(typeof(FeatureAttribute), true).Length > 0)
                               {
                                      //InfoTextBox.Text += mi.Name + "\r\n";
                                      foreach (object o in mi.GetCustomAttributes(typeof(FeatureAttribute), true))
                                      {
                                             FeatureAttribute fa = (FeatureAttribute)o;
                                             if (!features.ContainsKey(fa.AddedInVersion))
                                                    features.Add(fa.AddedInVersion, new List<FeatureAttribute>());
                                              features[fa.AddedInVersion].Add(fa);
                                      }
                               }
                         }
                  }
           }
     
           FileVersionInfo vi = System.Diagnostics.FileVersionInfo.GetVersionInfo(openFileDialog.FileName);
           InfoTextBox.Text = String.Format("{0} v{1} by {2}\r\n", vi.ProductName, vi.ProductVersion, vi.CompanyName);

           InfoTextBox.Text += "Available Features:\r\n\r\n";
     
           foreach (KeyValuePair<Version, List<FeatureAttribute>> kvp in features)
           {
                  InfoTextBox.Text += "v" + kvp.Key.ToString() + ":\r\n";
                  foreach (FeatureAttribute fa in kvp.Value)
                  {
                         InfoTextBox.Text += "  - " + fa.Name + "\r\n";
                  }
                  InfoTextBox.Text += "\r\n";
           }
    }

    First of all we open the assembly using the full file name and path stored in the OpenFileDialog. You could load an assembly residing in the GAC as well. Then we step to processing the methods residing within modules in the selected assembly. In this example we’re focusing only on the methods: in the real life you should iterate through all the scope your attributes support to be sure you’ll pick all the Features-marked items within the assembly. In the mi.GetCustomAttributes() method we pass the FeatureAttribute as desired type. You could obtain all the available attributes as well but it would only complicate processing them as you would need to obtain the type of each attribute and process each of it separately. As we iterate through the found attributes, we cast each of them to the FeatureAttribute class. As we have the attribute casted we can use its properties straight forward.

    As I’ve wanted to have all the features grouped per version and sorted descending on the version number, I’ve used the SortedDictionary generic collection with a custom comparer for the stored versions:

    private SortedDictionary<Version, List<FeatureAttribute>> features = new SortedDictionary<Version, List<FeatureAttribute>>(new VersionComparer());
    class VersionComparer : IComparer<Version>
    {
           #region IComparer<Version> Members
     
           public int Compare(Version x, Version y)
           {
                  return y.CompareTo(x);
           } 

           #endregion
    }

    The comparer isn’t that complicated: it makes use of the standard Version comparer but then in the different order what gives us the desired result.

    Now we move on to presenting the obtain data. As an extra I’ve obtained the product name, its version and the company name – all stored in the assembly. I did it using the FileVersionInfo class.

    To display all the features within all the versions we will move iteratively through the features collection. We use the KeyValuePair generic for obtaining the combinations. Each pair consists of a Version (Key) and a List of Features (Value). It’s all pretty easy to use as all the properties are type safe – all thank to generics.

    As we process our dummy assembly we get the following release notes:

    Generated Release Notes 

    It’s not much but I think it’s a good beginning for extending the client application with other attributes we’ve just defined, extending the attributes with new properties or maybe even defining some new ones. The idea of keeping the documentation right next to the code works for me pretty well – no more extra documents and files!

    As you might’ve noticed the GenerateReleaseNotes method contains some commented lines. You can check them out if you wish to get some more details on the way the assembly is actually being processed.

    I hope I’ve inspired you to having a look at the demo project and having some more fun with the custom attributes. Let me know if you have any questions regarding this post or custom attributes in .NET.

    You can download the solution I've used as well (Orcas format).

  • Imtech Tools moved to CodePlex

    As we've realised we have quite a few community tools we want to share, we figured out it would make it all easier to have a central file/release store. What's more, as we're busy with setting up a TFS development environment we have chosen for CodePlex as our main storage. From now on you can download all our community tools @ http://www.codeplex.com/tmt. All the links for our tools have already been edited.

  • Bugfix: Imtech Solution Setup (Free SharePoint Tool)

    A while ago I've published the 1.1.0.0 version of Imtech Solution Setup - a small tool which allows you to add the selected solution to the Solution Store and deploy it to Web Application of your choice - and all that using a Windows GUI. Today, while using the tool for a release of a Solution of one of our products for a demo we gave for Microsoft, I’ve found out that the Solution Setup doesn’t work properly. In spite of updating the Solution package it still deployed the old version of it.

    The problem was caused by the fact that the solution has been deployed to a Web Application. Somehow the force option didn’t have any influence on overwriting the old version of the Solution residing in the Solution Store with the new one. I’ve fixed this issue by first retracting the old solution from all the Web Applications it has been deployed to and then forcing the server to execute the timer jobs: just to be sure the environment will get cleaned up on time before adding the new version of the solution.

    I will post more on programmatically managing Solutions soon so stay in touch. In the meanwhile you can download the new version of the Imtech Solution Setup (Imtech Solution Setup v1.2.0.0 (44,8KB)) with the solution update bug fixed.

  • New internet-facing MOSS website goes live

    The Tech MOSS Team has build the new MOSS internet-facing for SURF (http://www.surf.nl) and we are very proud that it is live!
    The design was done by Uselab (http://www.uselab.com).

    Some technical highlights of this website are:
    - Seperate website for content editors
    - Multi-language support with variations
    - The use of Flash fonts in titles
    - Integration with Google search
    - Alternative theme navigation with filtering
    - Cool Flash slideshow webpart based on a SharePoint list


     

  • SharePoint Programmatically: Provisioning Lookup Fields

    Provisioning Content Type’s fields is quite straight forward when using Solutions: using Element Manifests you can define your own Content Types and their properties among which fields. Unfortunately there is one serious con to the solution Microsoft has offered: you simply cannot provision any Lookup Field using the Element Manifest.

    Lookup Fields obtain their values from an existing list. Each Lookup Field is being linked to its list using the list’s ID. As the ID’s are being generated after creating the instances there is no way to provision a Lookup Field linked to a newly created list during Solution deployment. That’s when Feature Receivers are useful. Feature Receivers are nothing more than custom code you can attach to events triggered by a feature. Provisioning a custom field programmatically works exactly the same as doing it using the GUI: first you need to create a Site Column and then you need to attach it to the Content Type of your choice.

    Assuming you have a solution with a feature called ContentDefinition responsible for deploying List Templates, provisioning List Instances and creating Site Columns and Content Types. It would look something close to:

    <Feature Id="GUID"
                  Title="Content Definition"
                  Description="Configures content definition"
                  Version="1.0.0.0"
                  Scope="Site"
                  Hidden="FALSE"
                  xmlns="http://schemas.microsoft.com/sharepoint/">
           <ElementManifests>
                  <ElementManifest Location="SiteColumns\SiteColumns.xml"/>
                  <ElementManifest Location="ContentTypes\ContentTypes.xml"/>
     
                  <!-- Lists -->
     
                  <!-- Custom List -->
                  <ElementFile