Posts Tagged ‘C#’

SPDisposeCheck How to suppress a false positive

Recently I asked a question on Twitter on how to suppress a false positive with the SPDispose check tool and Wictor Wilén pointed me to the right direction.
Several times I’m asked about the solution so I’m sharing it with you now. 

First of all you have to download the SPDisposeCheck tool and the SPDisposeCheckRules for Visual Studio 2010.
SPDisposeCheck tool: http://download.microsoft.com/download/B/4/D/B4D279A0-E159-40BF-A5E8-F49ABDBE95C7/SPDisposeCheck.msi
SPDisposeCheckRules for Visual Studio 2010: http://spdisposecheckstatic.codeplex.com/releases 

After downloading install the SPDisposeCheck tool on your machine and extract the zip file for the rules. The zip file contains a readme-vs2010.txt with instructions. Follow these instructions. 

The SPDisposeCheck tool is installed on the following location:
C:\Program Files (x86)\Microsoft\SharePoint Dispose Check 

At this location a zip file called SPDisposeExamplesSource.zip can be found. In this zip file several classes are stored, one of them is the class SPDisposeCheckIgnoreAttribute.cs.

To suppress a false positive you have to include this class in your project.

After including the attribute to suppress a false positive can be used:

[SPDisposeCheckIgnoreAttribute(SPDisposeCheckID.SPDisposeCheckID_110, "Don't want to do it")]
public void CreatingSPSiteLeak()
{
    SPSite siteCollection = new SPSite("http://moss");
    // siteCollection leaked
}

The above example is an example from another class in the zip file: SPSiteLeak.cs

SharePoint 2010 Custom Health Rule to check if the Solution Resource timerjobs are enabled when the Sandboxed Code Service is started

To monitor additional items not by default set up in SharePoint, custom health analyzer rules can be defined. For example the sandboxed solutions can be monitored a little bit better than SharePoint default provides.
When the Microsoft SharePoint Foundation Sandboxed Code Service is started sandboxed solutions can be deployed and used. To monitor the resources consumed by the sandboxed solutions a couple of timerjobs do the work of measuring and collecting the consumed resources:      

  • Solution Daily Resource Usage Update
  • Solution Resource Usage Log Processing
  • Solution Resource Usage Update

 

Wouldn’t it be nice to be sure the timerjobs are running when the Sandboxed Code Service is running?
Please continue reading on how to set this up.      

Building the rule

To set up a health analyzer rule, start with an empty SharePoint project. Add a class and inherit from SPHealthAnalysisRule. This is the class for the rule definition.
The class can also be inherited from SPRepairableHealthAnalysisRule. This means the rule can repair the problem. When using this the method Repair() has to be implemented as well.      

To define a rule some properties has to be overridden, like      

  • Category – Health Analyzer Rule Definitions are grouped by Category in the default view.
  • Summary – the title for the rule.
  • Explanation – displayed in the Health Reports list when the rule fails.
  • Remedy – displayed in the Health Reports list when the rule fails.
  • ErrorLevel – severity of the failure of the rule.

All rule definitions can be found in the rule definition list: Central Administration, Monitoring, Review rule definitions.
The Health Reports list can be found in the Central Administration, Monitoring, Review problems and solutions.      

The rule can be scheduled by overriding the AutomaticExecutionParameters property. In the get accessor return a SPHealthAnalysisRuleAutomaticExecutionParameters object.
When you don’t override the property the rule can be scheduled manually by a farm administrator.      

public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters
{
    get
    {
        SPHealthAnalysisRuleAutomaticExecutionParameters parameter = new SPHealthAnalysisRuleAutomaticExecutionParameters();
        parameter.Schedule = SPHealthCheckSchedule.Hourly;
        parameter.Scope = SPHealthCheckScope.Any;
        parameter.RepairAutomatically = false;
        parameter.ServiceType = typeof(SPTimerService);
        return parameter;
    }
}

In the code above the rule is scheduled on an hourly basis and the scope is set to SPHealthCheckScope.Any. This means the rule will run on the first available computer with the specified service.  

The code that identifies the real problem is the Check() method. The Check() method returns the outcome of the check: SPHeathStatus.Failed or SPHealthStatus.Passed. When Failed is the outcome of the check the rule will be displayed in the Health Reports list.


public override SPHealthCheckStatus Check()
{
    jobTitleOfDisabledJobs.Clear(); 

    SPUserCodeService userCodeService = SPUserCodeService.Local;
    if (userCodeService.IsEnabled)
    {
        using (SPSite site = new SPSite("your_site_url"))
            foreach (SPJobDefinition job in site.WebApplication.JobDefinitions)
            {
                switch (job.Title)
                {
                    case "Solution Daily Resource Usage Update":
                        AddJobTitle(job);
                        break;
                    case "Solution Resource Usage Log Processing":
                        AddJobTitle(job);
                        break;
                    case "Solution Resource Usage Update":
                        AddJobTitle(job);
                        break;
                    default:
                        break;
                }
            }

        if (jobTitleOfDisabledJobs.Count != 0)
        {
            return SPHealthCheckStatus.Failed;
        }
    }
    return SPHealthCheckStatus.Passed;
}

The above code first checks if the Sandboxes Code Service is started. If so, it checks if the timerjobs involved in measuring the resources consumed by the solutions are enabled.
The AddJobTitle() method adds the title(s) of the disabled job(s) to a generic list of strings:

private void AddJobTitle(SPJobDefinition job)
{
    if (job.IsDisabled)
    {
        jobTitleOfDisabledJobs.Add(job.Title);
    }
}

This list is used at the Explanation property to inform the farm administrator exactly which job(s) aren’t enabled:

public override string Explanation
{
    get
    {
        string jobTitles = string.Empty;
        for (int i = 0; i < jobTitleOfDisabledJobs.Count; i++)
        {
            jobTitles += jobTitleOfDisabledJobs[i].ToString();
            if (i != jobTitleOfDisabledJobs.Count - 1)
            {
                jobTitles += " / ";
            }
        }
        return "The Microsoft SharePoint Foundation Sandboxed Code Service is started, but not all the timerjobs are: " + jobTitles;
    }
}

Deploy the rule

To deploy the rule add a feature, scope it to farm level, and an event receiver to the feature.
Override the FeatureInstalled and FeatureUninstalling to register and unregister the rule:

public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
    try
    {
        Assembly currentAssembly = Assembly.GetExecutingAssembly();
        IDictionary<Type, Exception> exceptions = SPHealthAnalyzer.RegisterRules(currentAssembly); 

        if (exceptions != null)
        {
            if (exceptions.Count == 0)
            {
                //ok
            }
            else
            {
                //something went wrong, take appropriate action
            }
        }
    }
    catch (Exception ex)
    {
        throw new Exception("There was an error registering the health rule: " + ex.Message);
    }
} 

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
    try
    {
        Assembly currentAssembly = Assembly.GetExecutingAssembly();
        IDictionary<Type, Exception> exceptions = SPHealthAnalyzer.UnregisterRules(currentAssembly); 

        if (exceptions != null)
        {
            if (exceptions.Count == 0)
            {
                //ok
            }
            else
            {
                //something went wrong, take appropriate action
            }
        }
    }
    catch (Exception ex)
    {
        throw new Exception("There was an error removing the health rule: " + ex.Message);
    }
}

After the deployment, the rule can be found in the rule definition list: Central Administration, Monitoring, Review rule definitions. The default view at this list is showing all the defined rules EXCEPT the rules defined with the categoy System. Ofcourse it’s possible to create an additional view to show all the rules.     

Run and test the rule

The rule is now scheduled to run every hour. To speed things up select the rule and press Run Now. The timerjob which actually runs the Health Analyzer jobs, this is dependent of the schedule, is in this case Health Analysis Job (Hourly, Microsoft SharePoint Foundation Timer, Any Server). Make this timerjob also run now and check the Health Reports list.
Check if the rule fails when enabling and disabling the sandbox timerjobs which are checked in the rule. Note that the Explanation of the rule changes when different sandbox timerjobs are disabled or enabled.
Keep in mind when debugging the rule that you attach the debugger to the timer services: OWSTimer.exe process.

Issues

Register Assemly in GAC

When developing the rule the following error occurred in my environment:
Error occurred in deployment step ‘Add Solution’: Health analyzer rule registration requires that the {0} assembly be registered in the global assembly cache.     

The workaround for me was to adjust the Site URL property of the project before every deployment. I know it’s not a nice solution and likely not THE solution, but it worked for me and restarting several services and jobs didn’t do the trick unfortunately.      

Settings are not updated

If you change your mind on for example the category or explanation while developing it is possible that after the deployment of the solution with the changed items and the rule fails, the rule shows up at the ‘old’ category or with the ‘old’ explanation. This is one of the possible ‘issues’ which can occur if you don’t restart the Timer Service Recyle timerjob. This job is like an iisreset, but for the timer service, it recycles the Timer Service to free resources. 

Summary

Health Analysis Rules are not that hard to program, except a few things to know, but can be really usefull to have an overview of issues at the farm.

Paging with Client OM and CAML

CAML can be used in the Client OM to query for example a list. When the list contains a lot of items it’s not a good idea to show all the items at once. CAML supports row limits, so a  <RowLimit> element can be passed in the CAML query and show a subset of the results.
Maybe the user wants to browse through all the results, so all items has to be displayed on the page in some sort of form. Here paging of the results come in handy.  

To use paging, the ListItemCollectionPosition property of the ListItemCollection object can be used.
To keep track of paging the ListItemCollectionPosition of the CAML query has to be set to the ListItemCollectionPosition of your own object. This way the position of the starting point of the query can be set before querying the list and iterate through the pages until there are no pages left anymore.  

For testing purposes a Windows Forms application is created, with 2 buttons and a listbox.
The first button, named Start, gets the first 500 items of a list and displays the result as one item in the listbox. By pressing the second button, named Next, the next 500 results will be displayed until there are no pages left to display. When there are no pages left, the Next button is disabled. By pressing the Start button the process can be started again at the beginning.  

In this example a list called LargeList is used, the list contains 2500 items.
The CAML query used gets the Title field of an item and set the RowLimit to 500 items. After getting the items the ListItemCollectionPosition is stored in a property called itemPosition to keep track of the position of the starting point of the next query.
  

As soon as the itemPosition equals null, the results of the last page are gathered.  

The example code:


        public Microsoft.SharePoint.Client.ListItemCollectionPosition itemPosition { get; set; }
        public ClientContext context { get; set; }
        public Form1()
        {
            InitializeComponent();
        }
 

        private void button1_Click(object sender, EventArgs e)
        {
            listBoxResults.Items.Clear();
            buttonNext.Enabled = true;
 

            GetPage();
        }
 

        private void GetPage()
        {
            List largeList = context.Web.Lists.GetByTitle("LargeList");
            CamlQuery query = new CamlQuery();
            query.ListItemCollectionPosition = itemPosition;
            query.ViewXml = "<View><FieldRef Name='Title' /><RowLimit>500</RowLimit></View>";
            ListItemCollection collection = largeList.GetItems(query);
            context.Load(collection);
            context.ExecuteQuery();
 

            itemPosition = collection.ListItemCollectionPosition;
 

            string result = string.Empty;
            foreach (ListItem item in collection)
            {
                result += item["Title"].ToString();
            }
 

            if (itemPosition == null)
            {
                listBoxResults.Items.Add(result + Environment.NewLine + " Position: Last page " + collection.Count + " items");
                buttonNext.Enabled = false;
                return;
            }
 

            listBoxResults.Items.Add(result + Environment.NewLine + " Position: " + itemPosition.PagingInfo);
        }
 

        private void buttonNext_Click(object sender, EventArgs e)
        {
            GetPage();
        }
 

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            context.Dispose();
        }
 

        private void Form1_Load(object sender, EventArgs e)
        {
            context = new ClientContext("<your_context>");
        }
 

 

And the Windows Forms form:
 

A better user experience with the dialog framework and notifications

Today I am playing around with the dialog framework and simple ribbon buttons. I want the user to select one or more items from a list and update the Status field for all the items selected. The user can edit multiple items in datasheet view of course, but I’m making it just a little friendlier to the user.

I give myself two challenges for today:       

Add a button to the ribbon, enabling it when one or more items in the list are selected and update the Status field to ‘Completed’ of the selected items. This will be described at the paragraph ‘Just the button’ below.       

For more flexibility I will extend the above solution by a modal dialog where the user can choose a value for the Status column instead of setting the value to ‘Completed’ (and is not mentioned to the user anywhere), update the selected items and give the user a notification message if the update was successful and mentioning the Status value from the control on the modal dialog. This will be described at the paragraph ‘Modal dialog’ below.

The last option will give the user a better experience and will be a lot more flexible, because I’m not setting the value of the Status field in the code, but let the user choose from a predefined set of values.

Not really challenges, but I just want to point out a few things which are maybe interested to you. I’m not going to explain the basics of the ribbon or the dialog framework, because there are a lot of excellent posts out there.

Just the button
Add a button to the ribbon: please check out the cmdui.xml file located the SharePoint root folder, template\global\xml. Here you find a lot of definitions and examples you can use yourself and really helpful by positioning the controls on the ribbon.       

To just enable a button when one or more items in a list are selected is quite easy:
when defining a CustomAction with for example a button, you also define a CommandUIHandler. One of the attributes is EnabledScript:       

EnabledScript="javascript: function enableMarkAsCompletedButton(){
                            var items = SP.ListOperation.Selection.getSelectedItems();
                            return (items.length >= 1);
                          }

                          enableMarkAsCompletedButton();”

Check the above code to enable the button (in this case) only when one or more items are selected. Here the client object model for ECMA script is used to get the selected items.

Another attribute is CommandAction, the real action to occur when the button in the ribbon is clicked. The code used here:

CommandAction="javascript:
    var context = SP.ClientContext.get_current();
    var currentList = context.get_web().get_lists().getById(SP.ListOperation.Selection.getSelectedList());
    var items = SP.ListOperation.Selection.getSelectedItems();
    var singleItem;

    for (var i in items) {
        singleItem = currentList.getItemById(items[i].id);
        singleItem.set_item('Status', 'Completed');
        singleItem.update();
    }

    //actually submit
    context.executeQueryAsync(OnSuccess, OnError);

    function OnSuccess() {
        window.location.href = window.location.href;
    }

    function OnError(sender, args) {
        alert('Error' + args.get_message());

    } "

First grab the context and the list we’re at. The getSelectedList() is the input of the getById() function. The result is the actual list where later the getItemById() function can be used on.
Also check the for-loop. You can’t use items[i].id directly in the getItemById() function on the list, but you have to grab the id of the single item and use this as the input parameter. Don’t use the context.load() function, just execute the query asynchronously. The OnSuccess() function refreshes the page so the adjusted value can be seen and an alert is displayed when an error occurs.

Modal dialog
By using a modal dialog and give the result back to the user the user experience will be much better. I don’t use a predefined value in the code but let the user choose which Status to set on all the selected items at once. Because I’m using a dialog some additional text to explain the function and result better will be convenient for the user.

For displaying a modal dialog the function SP.UI.ModalDialog.showModalDialog(options) will be used. The options I’m going to use here are the url, width, height and the most important to explain here the dialogReturnValueCallback.
The url is a custom page which will be opened. At the dialogReturnValueCallback a callback function will be specified, this means what is going to happen when the user closes the dialog. At our case the selected items in the list will be updated with the new status.
A lot of the above code can be reused at this scenario:
enabling the button when one or more list items are selected will not change at all.
updating the list items will occur at a different moment, because we’re going to display a dialog first to let the user choose the Status for the selected items.

First let’s look at the showModalDialog(options) and the page that will be opened at the url. I put the page at a subdirectory of the _layouts folder, so referencing is easy.
In the page itself I put a dropdown control with some status values and a button to actually set the status. For better user experience I will list the selected items at the model dialog, so the user is still aware of what items are going to change. To list the items selected I will add the parameters listguid and the id’s of the items selected to the url at the options for the showModalDialog function:

var options = {
        url: '/_layouts/ITIdea.DialogFramework/PageSetStatus.aspx?listguid=' + SP.ListOperation.Selection.getSelectedList() + '&amp;items=' + selectedItems,
        height : 600,
        width : 500,
        dialogReturnValueCallback : CloseCallback};

        SP.UI.ModalDialog.showModalDialog(options);

And at the Page_Load function of the page I’m getting the listguid and selected items and display it in a simple label control on the page (please do not use this code as production code!):

        protected void Page_Load(object sender, EventArgs e)
        {
            if (Request.QueryString["listguid"] != null && Request.QueryString["items"] != null)
            {
                string list = Request.QueryString["listguid"].Substring(1, Request.QueryString["listguid"].Length - 2);
                Guid listGuid = new Guid(list);

                List<string> selectedItems = new List<string>();
                selectedItems.AddRange(Request.QueryString["items"].Split(','));

                SPList selectedList = SPContext.Current.Web.Lists[listGuid];

                string result = "Selected items: <br />";

                foreach (string item in selectedItems)
                {
                    result += selectedList.GetItemById(int.Parse(item)).Title + "<br />";
                }
 

                selectedItemsText.InnerHtml = result;
            }

        }

And this is how is looks like in its simplest form:

After the ‘Set’ button is selected the list items will be updated and for better user experience the user will be notified by a yellow notification on the right of the screen to what status the selected items are set. All this can be done at the function defined at dialogReturnValueCallback in the options of the showModalDialog, CloseCallback:

function CloseCallback(result, target) {
    if(result == SP.UI.DialogResult.OK){
      SP.UI.Notify.addNotification('Status value is set to ' + target, true);
      //set the status of all the selected items to the text
      AdjustFields(target);
    }
    if(result == SP.UI.DialogResult.Cancel){
      SP.UI.Notify.addNotification('Result is Cancel');
    }
}

function AdjustFields(newValue){
  var context = SP.ClientContext.get_current();
  var currentList = context.get_web().get_lists().getById(SP.ListOperation.Selection.getSelectedList());
  var items = SP.ListOperation.Selection.getSelectedItems();
  var singleItem;
  for (var i in items) {
      singleItem = currentList.getItemById(items[i].id);
      singleItem.set_item('Status', newValue);
      singleItem.update();
  }

  //actually submit
  context.executeQueryAsync(OnSuccess, OnError);

}

function OnSuccess() {
    setTimeout(window.location.href = window.location.href, 3000);
}

function OnError(sender, args) {
    alert('Error' + args.get_message());
}

The code for actually update the selected list items is moved to the AdjustFields function with newValue as input parameter and executed at the callback function of the dialog. This newValue is the value selected in the dropdown by the user at the modal dialog. How did we get that value over here?
Here another function of the SP.UI.ModalDialog comes in: commonModalDialogClose().
With this function you can set your own return value which will be used at the callback function of the showModalDialog(). Real nice function!!
To set a custom return value for the callback function, I use the commonModalDialogClose at the click eventhandler for the Set button at the page which is displayed as the modal dialog:

function SetNewStatus_Click() {
            var resultText = document.getElementById('StatusChoices').value;
            SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.OK, resultText);
        }

In the above code the element ‘StatusChoices’ is the id of the dropdown control with the different status values. With the commonModalDialogClose() the dialog result is set to OK and the result value is set to the selected value in the dropdownbox.
By setting the result to OK, the code in the CloseCallback() function will execute the code in the first if-statement. Here the message for the notification is set and the return value (target parameter, which was set at SetNewStatus_Click() ) is added to the notification message.
       

After explaining this simple example you’re getting the idea of a better user experience with modal dialogs and notification messages. Inform the user as much as you can, the options are given to you by SharePoint and who are we not using them…

Permormance issue GetItemById

Be careful with the use of GetItemById(), there are two options to use it:
SPList.Items.GetItemById()
SPList.GetItemById()

The difference between the two is that the SPList.Items.GetItemById() loads every item into memory and then filters the set. Trust me: this can cause severe perfomance issues when using this on lists with a large number of items.
SPList.GetItemById() doesn’t do this and is way faster because of this. Be warned!

BCS model – a simple editable model

Let’s adjust the previous model to enable adding new items and edit existing ones. 

Open the previous project and open the design view of the model (.bdcm). In the BDC Method Details window select ‘Add a method’ and ‘Create Creator Method’. Two parameters are created, one for input, one as return parameter. Check the type names of both Type Descriptors. Both have to be set to the Customer entity. Probably they are set correctly already, because of the previous ReadList and ReadItem methods. Open the BDC Explorer and make sure the other type descriptors (the ones that make up a Customer) are present at the input and return parameter. Probably they also are there already.
Check the properties of the type descriptors of the input parameter ‘NewCustomer’. The property CreatorField is set to True here. With this parameter you can control the fields displayed on the new form. Since our CustomerId is an auto increment field, delete this type descriptor from the input parameter.
That’s it for the configuration, the only thing left is to update the code at the CustomerService class. As in the previous post I use Linq to Sql, an example:

        public static Customer CreateCustomer(Customer newCustomer)
        {
            using (LinqToSQLClassCustomerDataContext db = new LinqToSQLClassCustomerDataContext(CONNECTION_STRING))
            {
                CustomerBasic newItem = new CustomerBasic();
                newItem.CustomerName = newCustomer.CustomerName;
                newItem.CustomerCity = newCustomer.CustomerCity;
                db.CustomerBasics.InsertOnSubmit(newItem);
                db.SubmitChanges(); 

                Customer returnCust = new Customer
                {
                    CustomerId = newCustomer.CustomerId
                };

                return returnCust;
            }
        }

Deploy the solution and check out the list created on the previous version of this solution. The list seems fine and at the List Tools tab, Items, the New Item button is enabled. Select this button.
When expecting the new form, a Runtime Error occurs.
Open SharePoint Designer and select the list. In the next screen several items of the list are displayed, e.g. Settings, Views and… Forms! As you can see at the picture below there is no NewForm, that’s why the error occurs.
 

And checking the logfile:
SPException thrown: Message: Unable to find the default new form for list
Says the same, no New Form present. 

To get a full functioning list, create a new list based on the External Content Type and the NewForm is present:
  

Ofcourse SharePoint Designer can help you out without creating a new list by selecting NewForm at the Forms section:

When selecting ‘New Item’ in the browser a new Customer can be added.
    

The updater method is just as simple.
In the BDC Method Details window select ‘Add a method’ and ‘Create Updater Method’. One input parameter is created ‘customer’. Check the type name of the Type Descriptor. This has to be set to the Customer entity. Probably it is set correctly already, because of the previous defined methods. Open the BDC Explorer and make sure the other type descriptors (the ones that make up a Customer) are present at the input parameter. Probably they also are there already. Set the Read Only property of the CustomerId type descriptor to false and leave the Updater Field property set to True.    

Now update the code for the updated method to actually update the Customer.

        public static void UpdateCustomer(Customer customer)
        {
            using (LinqToSQLClassCustomerDataContext db = new LinqToSQLClassCustomerDataContext(CONNECTION_STRING))
            {
                CustomerBasic updateItem = (from c in db.CustomerBasics
                                            where c.CustomerId == customer.CustomerId
                                            select c).FirstOrDefault();
                updateItem.CustomerName = customer.CustomerName;
                updateItem.CustomerCity = customer.CustomerCity;
                db.SubmitChanges();
            }
        }

 

Deploy the solution and take a look at the list created with the previous version of the solution. Same story: Runtime Error; Edit Form not present. SharePoint Designer to the rescue: create a new Edit Form and voila!

Set Developer Dashboard level by Visual Studio

In my previous post about the Developer Dashboard I showed you how to switch it on/off/on demand by PowerShell.
Ofcourse this can be done in Visual Studio as well:

SPWebService cs = SPWebService.ContentService;
cs.DeveloperDashboardSettings.DisplayLevel = (SPDeveloperDashboardLevel)selectedValue;
cs.DeveloperDashboardSettings.Update();

Make sure, if you make a webpart to set the developer dashboard level, you deploy this as a Farm Solution and deploy it to the Central Administration (Project Properties, set Site URL to your CA location).
The code will not work on another site, only at the CA, because the developer dashboard is a farm wide setting. If you do try to use the webpart in another site than CA, a security exception will be thrown:
System.Security.SecurityException: Access denied.

When you deploy the solution to the CA and try to use it on another site (or visa versa) you get the message:
A Web Part or Web Form Control on this Page cannot be displayed or imported. The type is not registered as safe.

After deploying a webpart to set the level of the Developer Dashboard, just go to the Central Administration e.g. to the Application Management page and edit the page. Two webpart zones will be visible and just add the webpart as you normally would.
An example:

SharePoint 2010

 

September 2009 – heden

SharePoint 2010 consultant/developer

 

Buiten MOSS 2007 is Anita ook op SharePoint 2010 gebied zeer gedreven. Vanaf december 2009 is ze actief bezig met allerlei aspecten van SharePoint 2010 en heeft veel evenementen bijgewoond op SharePoint 2010 gebied waaronder Microsoft SharePoint Connections (2 dagen) in Amsterdam en SharePoint 2010 Evolution Conference (3 dagen) in Londen.

Ter voorbereiding op het examen voor developers van SharePoint 2010 heeft Anita de nieuwe onderdelen van SharePoint 2010 uitvoerig bestudeerd en zelf uitgeprobeerd middels het schrijven van code (C#). Specifieke SharePoint 2010 onderdelen welke aan de orde zijn geweest zijn sandboxed solutions, full-trust proxies, BCS, workflows, web analytics, custom rating icons, dialogs, ribbon customizations (ook contextueel), client object model, LINQ to SharePoint en taxonomy (term store). 

SharePoint 2010, Microsoft Office SharePoint Designer 2010, Visual Studio 2010, jQuery.

Documents and versioning

A nice part of document libraries and lists is the version history. Nothing new, we had it in MOSS 2007, but I previously never dived really into the version history by code.
Recently I did and discovered something that might help you out some time. 

Let’s get the files of the Shared Documents folder and get all the versions by code:

SPDocumentLibrary sharedDocs = SPContext.Current.Web.Lists['Shared Documents'] as SPDocumentLibrary;
resultBox.Text = string.Empty;
foreach (SPListItem doc in sharedDocs.Items)
{
      SPFileVersionCollection coll = doc.File.Versions;
      if (coll.Count != 0)
     {
          resultBox.Text += 'Versions of ' + doc.File.Name + Environment.NewLine;
          foreach (SPFileVersion version in coll)
          {
                resultBox.Text += 'VersionLabel: ' + version.VersionLabel + ' IsCurrentVersion: ' + version.IsCurrentVersion + Environment.NewLine;
          }
     }
}

 Nothing quite impressive, I used the file version collection and asked some of the properties from the SPFileVersion. Here’s the result:

Notice that version 5.0 is the currently published version and a draft version 5.1 is available too.
Now let’s take a look at the version history of the file at the document library itself:

 

Hey, there is another version: 5.2! Why does this file not show up by code? 

There is another approach to get all the versions, let’s use the versions of the list items instead of the files: 

SPDocumentLibrary sharedDocs = SPContext.Current.Web.Lists['Shared Documents'] as SPDocumentLibrary;
resultBox.Text = string.Empty;
foreach (SPListItem doc in sharedDocs.Items)
{
     SPListItemVersionCollection coll = doc.Versions;
     resultBox.Text += 'Versions of  ' + doc.Name + Environment.NewLine;
     foreach (SPListItemVersion version in coll)
     {
          resultBox.Text += 'VersionLabel: ' + version.VersionLabel + ' IsCurrentVersion: ' + version.IsCurrentVersion + Environment.NewLine;
     }
}

And the result:
 

Notice that all the existing versions, including the current version, are displayed. Also the sorting is the other way around compared to the listing based on the file versions. 

The conclusion here is that the SPFileVersionCollection only shows the versions, without the current version and the SPListItemVersionCollection shows all the versions, including the current version

Suppose you want to delete or put all the previous versions of a file to the recycle bin, you can easily use the SPFileVersionCollection option demonstrated above. This way you don’t have to exclude the current version from your deleting code as you would with the SPListItemVersionCollection option. 

Here’s an example of this:

SPDocumentLibrary sharedDocs = SPContext.Current.Web.Lists['Shared Documents'] as SPDocumentLibrary;
resultBox.Text = 'To recycle bin: ' + Environment.NewLine;
foreach (SPListItem doc in sharedDocs.Items)
{
     SPFileVersionCollection coll = doc.File.Versions;
     if (coll.Count != 0)
     {
          resultBox.Text += doc.Name + Environment.NewLine;
          doc.File.Versions.RecycleAll();
     }
}

The result of questioning the SPFileVersionCollection after this action:

The result of questioning the SPListItemVersionCollection after this action:

Notice that the current version of the file still exists after the recycle action. With the SPFileVersionCollection there is one version besides the actual current version, which is not listed here. This is because the actual current version is still a draft version.
With the SPListItemVersionCollection both versions are listed, the top one is the draft, the second the published version.

FrieslandCampina

 

Branche: Zuivel

September 2009 – oktober 2010

SharePoint consultant/developer

 

Koninklijke FrieslandCampina voorziet miljoenen mensen in meer dan honderd landen verspreid over de hele wereld van melkproducten, kaas, boter en ingrediënten. Voor meer informatie: http://www.frieslandcampina.com

Dairy News & Analysis is de intranet site van FrieslandCampina’s Corporate Strategy afdeling. DNA is opgezet om goede gefundeerde beslissingen te kunnen maken in business tactieken en strategieën.

Het DNA project waaraan Anita heeft gewerkt betrof de migratie van DNA op LiveLink naar SharePoint (MOSS 2007). Binnen dit project zijn meer dan 50.000 documenten op LiveLink gemigreerd naar SharePoint met behoud van de metadata.

Anita heeft in dit project maatwerk webparts, timerjobs en een controltemplate (custom column type, cascading dropdown, multichoice, jQuery) gebouwd om de werking van de DNA applicatie optimaal op SharePoint te faciliteren.

Notificaties kunnen worden ingesteld om nieuwe items zowel per email en/of op een blackberry te kunnen ontvangen. De email en blackberry berichten zijn volledig opgemaakt volgens de FrieslandCampina huisstijl en worden op een vast tijdstip, met keuze dagelijks of wekelijks, verstuurd naar de geabonneerde. Gebruikers van het systeem kunnen zelf opgeven van welke informatie zij op de hoogte gehouden willen worden.

Met de ontwikkelde webparts kunnen verschillende dwarsdoorsneden worden gemaakt van de meer dan 50.000 documenten welke in SharePoint zijn opgeslagen. Hierbij wordt gebruik gemaakt van SharePoint Search Scopes en managed properties. De verschillende zoekmogelijkheden zijn zowel met CAML als met SharePoint Enterprise Search SQL statements geïmplementeerd, afhankelijk van de wens of de data per direct beschikbaar moet zijn voor gebruikers, bijvoorbeeld dagelijks nieuws, of dat een vertraging acceptabel is, bijvoorbeeld bij het maken van analyses waarbij veel data wordt opgevraagd.

Verder wordt de prijsontwikkeling van verschillende grondstoffen bijgehouden. Hiervoor heeft Anita een custom grafiek module geprogrammeerd, voor het gebruik in SharePoint, welke de data grafisch weergeeft. De grafiek met de ruwe data kan geëxporteerd worden als pdf of MS Excel formaat. Bij het exporteren wordt een vaste volgorde van de weergave van de gegevens gehanteerd inclusief een inhoudsopgave. De keuzes welke de gebruiker heeft gemaakt voor het genereren van de grafiek, bijvoorbeeld tijdsperiode en grondstof, worden overgenomen in het rapport. De opmaak van het rapport is geheel volgens FrieslandCampina huisstijl.

Om bij te houden hoeveel en welke soorten gegevens er in het systeem gebruikt wordt is er een timerjob ontwikkeld welke periodiek aantallen van bijvoorbeeld type documenten en andere gebruikte bronnen verzamelt in een lijst. Met behulp van jQuery en SPServices worden de gegevens overzichtelijk aan de gebruiker gepresenteerd.

SPServices in combinatie met jQuery is veelvuldig gebruikt om gegevens overzichtelijk aan gebruikers te presenteren, bijvoorbeeld een aangepaste weergave van een lijst met ondernemingen welke in het systeem aanwezig zijn en het tonen en verbergen van tabbladen op basis van aanwezige gegevens. In het systeem zijn ook TV Commercials te raadplegen middels een geavanceerd zoekscherm opgebouwd met SPServices en jQuery.

Het ontwikkelde maatwerk is in features in combinatie met solutions opgeleverd.

Naast dit project, wat de hoofdopdracht was, heeft Anita gewerkt aan andere SharePoint zaken als het uitbreiden van het Content Query webpart en aanpassen van de bijbehorende xslt, het maken van een newsticker (à la CNN, tv) op basis van een extern aangeboden rss feed en het maken van aspx pagina’s met dataviews en connected webparts met behulp van SharePoint Designer. 

Voor een interview over het DNA project voor het internet personeelsblad Spark, klik hier.

Microsoft Office SharePoint Server 2007, Microsoft Office SharePoint Designer 2007, Visual Studio 2008, javascript, jQuery, SPServices, xslt, Visual Source Safe, MS Chart Controls