Second Life of a Hungarian SharePoint Geek

January 29, 2011

How to get the assembly version number from the build date and vice versa?

Filed under: Utility, Visual Studio, WPF — Tags: , , — Peter Holpar @ 00:36

Recently I worked on a project, where automatic assembly versioning of Visual Studio 2010 was applied using the AssemblyVersion attribute:

[assembly: AssemblyVersion("1.0.*")]

Testers worked with multiple application versions simultaneously, and version number (that was displayed on the application UI) was assigned to the bug report in TFS work items. To be able to determine the latest changeset for a version number, I needed the exact date and time of the build.

I found several none-official description (with minor differences) of the build date – version number conversion algorithm, like this one:

Determining the Build Date of an Assembly

I decided to create a simple utility to help the conversion in both directions.

The main part of the utility is an IValueConverter class that contains the logic for the conversion:

  1. using System;
  2. using System.Linq;
  3. using System.Windows.Data;
  4. using System.Globalization;
  5.  
  6. namespace Version2Date
  7. {
  8.     class VersionToDateConverter : IValueConverter
  9.     {
  10.         // based on the algorithm found:
  11.         // Kevin Gearing's Blog – Determining the Build Date of an Assembly
  12.         // http://dotnetfreak.co.uk/blog/archive/2004/07/08/determining-the-build-date-of-an-assembly.aspx
  13.  
  14.         #region IValueConverter Members
  15.  
  16.         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  17.         {
  18.             String result = String.Empty;
  19.  
  20.             DateTime versionDate = DateTime.MinValue;
  21.  
  22.             // check the value type and try to parse it as a date
  23.             if ((value is String) && (DateTime.TryParseExact((String)value, Global.DateFormat,
  24.                 CultureInfo.GetCultureInfo("en"), DateTimeStyles.None, out versionDate)))
  25.             {
  26.                 if (TimeZone.IsDaylightSavingTime(versionDate, TimeZone.CurrentTimeZone.GetDaylightChanges(versionDate.Year)))
  27.                 {
  28.                     versionDate.AddHours(-1);
  29.                 }
  30.  
  31.                 TimeSpan dateDiff = versionDate – Global.StartDate;
  32.                 int dayCount = (int)dateDiff.TotalDays;
  33.                 TimeSpan dateDiffSec = dateDiff.Subtract(new TimeSpan(dayCount, 0, 0, 0));
  34.                 int secCount = (int)(dateDiffSec.TotalSeconds / 2);
  35.                 result = String.Format("1.0.{0}.{1}", dayCount, secCount);
  36.             }
  37.  
  38.             return result;
  39.         }
  40.  
  41.         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  42.         {
  43.             String result = String.Empty;
  44.  
  45.             if (value is String)
  46.             {
  47.                 String[] verNums = ((String)value).Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
  48.  
  49.                 int dummyInt;
  50.                 if ((verNums.Length == 4) && (verNums.ToList().TrueForAll(str => int.TryParse(str, out dummyInt))))
  51.                 {
  52.                     int dayCount = int.Parse(verNums[2]);
  53.                     int secCount = int.Parse(verNums[3]) * 2;
  54.  
  55.                     DateTime versionDate = Global.StartDate.AddDays(dayCount).AddSeconds(secCount);
  56.                     if (TimeZone.IsDaylightSavingTime(versionDate, TimeZone.CurrentTimeZone.GetDaylightChanges(versionDate.Year)))
  57.                     {
  58.                         versionDate.AddHours(1);
  59.                     }
  60.  
  61.                     result = versionDate.ToString(Global.DateFormat);
  62.                 }
  63.             }
  64.  
  65.             return result;
  66.         }
  67.  
  68.         #endregion
  69.     }
  70.  
  71. }

The MainWindow.xaml of the application looks like this:

  1. <Window x:Class="Version2Date.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
  4.         xmlns:local="clr-namespace:Version2Date"
  5.         Title="Version – Date Converter"
  6.         Height="45" Width="400" ResizeMode="NoResize">
  7.     <Window.Resources>
  8.         <local:VersionToDateConverter x:Key="VersionToDate"/>
  9.     </Window.Resources>
  10.     <Grid>
  11.         <Grid.ColumnDefinitions>
  12.             <ColumnDefinition Width="Auto"></ColumnDefinition>
  13.             <ColumnDefinition Width="*"></ColumnDefinition>
  14.             <ColumnDefinition Width="*"></ColumnDefinition>
  15.         </Grid.ColumnDefinitions>
  16.         <TextBox Grid.Column="0" Name="VersionNumber" Text="{Binding ElementName=Date, Mode=TwoWay,
  17.             UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True,
  18.             Path=Text, Converter={StaticResource VersionToDate}}" MinWidth="100" MaxWidth="200"/>
  19.         <TextBox Grid.Column="1" Name="Date" MinWidth="100" MaxWidth="200" />
  20.         <Button Grid.Column="2" Click="GetCurrentDate" MinWidth="100" MaxWidth="200">Now</Button>
  21.         
  22.     </Grid>
  23. </Window>

The code behind of the XAML contains the simple event handler for the button click:

  1. private void GetCurrentDate(object sender, RoutedEventArgs e)
  2. {
  3.     Date.Text = DateTime.Now.ToString(Global.DateFormat);
  4. }

The next image displays the utility in action:

image

January 27, 2011

How to check if a specific list or folder exists using the managed client object model

Filed under: Managed Client OM, SP 2010 — Tags: , — Peter Holpar @ 23:10

A few days ago I read a question on the SharePoint 2010 developer MSDN forum where part of the request was to check for the existence of a specific folder within a specific list. In the answer the check is simply done applying a try / catch block (try to get it and if it fails, create it).

One can read similar suggestions in this MSDN technical article as well:

“You should be prepared to catch exceptions when you write code that may fail if you ask for objects that may not exist.”

I agree that you should be prepared for this case but I’m pretty sure it should not be your default path. Exception handling should remain what its name suggests: handling exceptions, special cases. If there is other way to check object existence you should use that at first place, and not use exception handling to define program flow logic. The latter one may have serious performance issues and results typically quick-and-dirty “solutions”.

But how can we check whether an object exists or not? Let’s see a code sample for lists and folders:

  1. public Folder GetFolderCS(ClientContext clientContext, String listTitle, String folderName)
  2. {
  3.     Folder existingFolder = null;
  4.  
  5.     Web web = clientContext.Web;
  6.     ListCollection lists = web.Lists;
  7.  
  8.     List list = GetListByTitleCS(clientContext, listTitle);
  9.  
  10.     if (list != null)
  11.     {
  12.         FolderCollection folders = list.RootFolder.Folders;
  13.  
  14.         String folderUrl = String.Format("/{0}/{1}", listTitle, folderName);
  15.  
  16.         IEnumerable<Folder> existingFolders = clientContext.LoadQuery<Folder>(
  17.             folders.Where(
  18.             folder => folder.ServerRelativeUrl == folderUrl)
  19.             );
  20.         clientContext.ExecuteQuery();
  21.  
  22.         existingFolder = existingFolders.FirstOrDefault();
  23.     }
  24.  
  25.     return existingFolder;
  26. }
  27.  
  28.  
  29. public List GetListByTitleCS(ClientContext clientContext, String listTitle)
  30. {
  31.     List existingList;
  32.  
  33.     Web web = clientContext.Web;
  34.     ListCollection lists = web.Lists;
  35.  
  36.     IEnumerable<List> existingLists = clientContext.LoadQuery(
  37.             lists.Where(
  38.             list => list.Title == listTitle)
  39.             );
  40.     clientContext.ExecuteQuery();
  41.  
  42.     existingList = existingLists.FirstOrDefault();
  43.  
  44.     return existingList;
  45. }

In the above code we ask for the folder and list having the specified name and title. If the result is null the object does not exist.

If you call the GetFolderCS method using the exact list title and folder name of an existing list and folder, you get the folder object:

ClientContext clientContext = new ClientContext(http://sp2010);
Folder folder = GetFolderCS(clientContext, "Pages", "folder1");

If you test the solution further, you may found, that it is unfortunately case sensitive (that is the CS postfix in the method names). For example, if you type pages for a list named Pages, the result will be null, suggesting a non-existing list. That is due to the case sensitivity of the lambda expression used in the LoadQuery method of the ClientContext class.

Unfortunately, trivial methods to workaround this issue do not help, you will get ClientRequestException for the following trials:

list => list.Title.ToLower() == listTitle.ToLower()
The ‘ToLower’ member cannot be used in the expression.

list => list.Title.IndexOf(listTitle) == 0 && list.Title.Length == listTitle.Length
The ‘IndexOf’ member cannot be used in the expression.

list => list.Title.Equals(listTitle, StringComparison.CurrentCultureIgnoreCase))
The ‘Equals’ member cannot be used in the expression.

So what can we do to create a case insensitive version?

The solution is to get the list of the lists and the list of the folders within the list and compare its title and name locally as show in this slightly modified version:

  1. public Folder GetFolderCI(ClientContext clientContext, String listTitle, String folderName)
  2. {
  3.     Folder existingFolder = null;
  4.  
  5.     Web web = clientContext.Web;
  6.     ListCollection lists = web.Lists;
  7.  
  8.     List list = GetListByTitleCI(clientContext, listTitle);
  9.  
  10.     if (list != null)
  11.     {
  12.         FolderCollection folders = list.RootFolder.Folders;
  13.  
  14.         String folderUrl = String.Format("/{0}/{1}", listTitle, folderName);
  15.  
  16.         IEnumerable<Folder> existingFolders = clientContext.LoadQuery(
  17.             folders.Include(
  18.             folder => folder.ServerRelativeUrl)
  19.             );
  20.         clientContext.ExecuteQuery();
  21.  
  22.         existingFolder = existingFolders.FirstOrDefault(
  23.             folder => folder.ServerRelativeUrl.ToLower() == folderUrl.ToLower());
  24.     }
  25.  
  26.     return existingFolder;
  27. }
  28.  
  29.  
  30. public List GetListByTitleCI(ClientContext clientContext, String listTitle)
  31. {
  32.     List existingList;
  33.  
  34.     Web web = clientContext.Web;
  35.     ListCollection lists = web.Lists;
  36.  
  37.     IEnumerable<List> existingLists = clientContext.LoadQuery(
  38.              lists.Include(
  39.              list => list.Title)
  40.              );
  41.     clientContext.ExecuteQuery();
  42.  
  43.     existingList = existingLists.FirstOrDefault(list => list.Title.ToLower() == listTitle.ToLower());
  44.  
  45.     return existingList;
  46. }

In this case you can use the ToLower method that makes the result case insensitive.

I hope this version will help you to check for list and folder existence without misusing exception handling.

January 26, 2011

Creating external lists using the managed client object model

Filed under: BCS, External list, Managed Client OM, SP 2010 — Tags: , , , — Peter Holpar @ 23:06

Last year I wrote a post about how to create external lists in SharePoint 2010 using server side code. Now I show you how to achieve the same result from the client side.

The solution does not differ too much, there are only two minor differences:

  1. External data source property names are not exposed as public fields (see the members of the SPListDataSource.BDCProperties class on the server side).
  2. When adding the new external list to the SPListCollection, you have to use the Add method that has three String parameters (title, description and url) and a dataSource parameter of SPListDataSource type. The result of the call is the ID (Guid) of the new list.
    On the client side we have use the Add method of the ListCollection class that has a single parameters parameter of type ListCreationInformation. The ListCreationInformation class represents all the information (list title, description, URL, data source properties, and a few more info) we used on the server side. The return type of the Add method is List. Another Important difference is that in this case the data source properties are not represented as a SharePoint-specific type, like SPListDataSource on the server side, but they are collected into a DataSourceProperties property that is a “standard” .NET  IDictionary<string, string> type. The additional information in ListCreationInformation contains for example a QuickLaunchOption property of enum type  QuickLaunchOption (possible values Off, On and DefaultValue).

After this introduction let’s see the code.

First, I’ve created an extension method in a static helper class to make my life easier and to avoid mistyping parameter names:

  1. // the listDataSource parameter is only a fake one to enable attaching the static method to ListDataSource type
  2. public static IDictionary<String, String> Initialize(this ListDataSource listDataSource, String entity, String entityNamespace, String lobSystemInstance, String specificFinder)
  3. {
  4.     // BDCProperties (SPListDataSource internal class) are not available from client code, we should use string literals
  5.     Dictionary<String, String> result = new Dictionary<String, String>();
  6.     result.Add("Entity", entity);
  7.     result.Add("EntityNamespace", entityNamespace);
  8.     result.Add("LobSystemInstance", lobSystemInstance);
  9.     result.Add("SpecificFinder", specificFinder);
  10.  
  11.     return result;
  12. }

As you can see, the listDataSource parameter is not used in the class. My purpose was only to bind the functionality to the ListDataSource class and keep the working of the server-side and client-side codes as parallel as it is possible.

The following code demonstrates the creation of the external list from the managed client API using the extension method we created above.

  1. ClientContext clientContext = new ClientContext(siteUrl);
  2. Web web = clientContext.Web;
  3.  
  4. ListCollection lists = web.Lists;
  5.  
  6. ListDataSource listDataSource = new ListDataSource();
  7. // set up the list data source properties using the extension method (see Extensions.cs)
  8. IDictionary<String, String> listDataSourceProp = listDataSource.Initialize("YourBdcEntity", "YourBdc.EntityNamespace", "YourLobSystemInstancece", "ReadItem");
  9.  
  10. // initialize create list info
  11. // IMPORTANT! The DataSourceProperties is not of type ListDataSource,
  12. // as one may expect, but a simple IDictionary<String, String>
  13. ListCreationInformation listCreateInfo = new ListCreationInformation
  14. {
  15.     Title = listTitle,
  16.     Description = "List description",
  17.     DataSourceProperties = listDataSourceProp,
  18.     Url = "listurl",
  19.     QuickLaunchOption = QuickLaunchOptions.On
  20. };
  21.  
  22. // create list
  23. List newList = lists.Add(listCreateInfo);
  24.  
  25. clientContext.ExecuteQuery();

I made some test if I can to change the data source binding of a list after creation.

The List class contains a DataSource property of type ListDataSource, but it is read-only so you cannot make a standard list to  an external one, or change the data source parameters simply like:

newList.DataSource = listDataSource;

The above code lines produces a compile time error.

I’ve also tested what happens if I change the properties of the data source one-by-one like shown here:

  1. clientContext.Load(newList, list => list.DataSource);
  2. clientContext.ExecuteQuery();
  3.  
  4. newList.DataSource.Properties["SpecificFinder"] = "AnotherSpecificFinder";
  5. clientContext.ExecuteQuery();

Using this code I’ve experienced neither compile-time nor run-time error, but when I checked the value of the property later using this code:

  1. clientContext.Load(existingList, list => list.DataSource);
  2. clientContext.ExecuteQuery();
  3. Console.WriteLine(existingList.DataSource.Properties["SpecificFinder"]);

I found that the value of the property has not been changed. So it seems to be a good idea to plan and set the properties of your external lists in advance.

January 9, 2011

Speech enable your web pages using Silverlight, WCF and the Managed Speech API

Filed under: Silverlight, Text-To-Speech, WCF — Tags: , , — Peter Holpar @ 23:58

Integrating speech-related functionality (either speech synthesis or speech recognition) into custom applications is one of the many areas I am interested in since years, but never had enough time to delve into.

In the last few months I read several articles about speech-capabilities of Silverlight. A few days ago I found this article where Brian Lagunas shows us how to create a WCF bridge between Silverlight and server-side Managed Speech API. I liked his idea and decided to take this approach one step further combining it with HTML events. My idea was to enable users to select web page text in the browser, similar like in my former post about IE add-ins, but in this case the application should “read” the selected text instead of simply copying it into a text box.

You can watch this screencast about how to handle HTML events in Silverlight. In my case I had to handle the onmouseup event, when the user releases up the mouse button after selecting the text.

To help debugging, I’ve included a text box, called SelectedText in the page XAML. Beside sending the selected text back to the server for speech conversion, we also copy the selection into this text box. This text box can be removed if you don’t need that anymore.

There is a simple sample sentence on the host page that you can use to test the feature.

image

Of course, in a real page you don’t want the Silverlight application to be visible at all. To hide the Silverlight UI, you cannot use the style="display:none", since it will cause  the Silverlight app not to load. Instead, you should set its height and with to zero as shown below. You can read more about this behavior in this forum thread.

  1.     <div id="silverlightControlHost"> <!– style="display:none" doesn't work –>
  2.         <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="600" height="120" > <!– width="0" height="0" –>
  3.           <param name="source" value="ClientBin/SpeechApp.xap"/>
  4.           <param name="onError" value="onSilverlightError" />
  5.           <param name="background" value="white" />
  6.           <param name="minRuntimeVersion" value="4.0.50826.0" />
  7.           <param name="autoUpgrade" value="true" />
  8.           <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0&quot; style="text-decoration:none">
  9.               <img src="http://go.microsoft.com/fwlink/?LinkId=161376&quot; alt="Get Microsoft Silverlight" style="border-style:none"/>
  10.           </a>
  11.         </object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>

Let’s see the main code of the Silverlight application. In the OnLoad event we subscribe to the onmouseup HTML event. The OnMouseUp method is responsible to handle this event. It first determines the selected text (more about that a bit later), copies it to the text box, and if it is not empty, calls the WCF service to get the speech response the very same way as it is done in Brian’s post.

  1. void OnLoaded(object sender, RoutedEventArgs e)
  2. {
  3.     HtmlDocument doc = HtmlPage.Document;
  4.     doc.AttachEvent("onmouseup", OnMouseUp);
  5. }
  6.  
  7. void OnMouseUp(object sender, HtmlEventArgs e)
  8. {
  9.     String selection = (String)HtmlPage.Window.Invoke("getSelection");
  10.     SelectedText.Text = selection;
  11.  
  12.     if (!String.IsNullOrEmpty(selection))
  13.     {
  14.         SpeechServiceClient client = new SpeechServiceClient("BasicHttpBinding_ISpeechService");
  15.         client.SpeakCompleted += (o, ea) =>
  16.         {
  17.             WavMediaStreamSource audioStream = new WavMediaStreamSource(new MemoryStream(ea.Result));
  18.             AudioPlayer.SetSource(audioStream);
  19.         };
  20.         client.SpeakAsync(SelectedText.Text);
  21.     }
  22. }

Unfortunately, I was not able to get the selected text directly from Silverlight, so I’ve included the following JavaScript method in the hosting page, and call that method to get the selection.

  1. function getSelection()
  2. {
  3.     var currentSelection = document.selection;
  4.     var result = "";
  5.  
  6.     if ((currentSelection != null) && (currentSelection.type == "Text")) {
  7.         range = currentSelection.createRange();
  8.         if (range != null) {
  9.             result = range.text;
  10.         }
  11.     }
  12.  
  13.     return result;
  14. }

You can download the code for this application here. This code is really does not differ too much from the original version expect of the few code snippets illustrated above. Thanks to Brian for the original idea and implementation as well as his permission to make this modification and publish the results.

Theme: Shocking Blue Green. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 42 other followers