Tuesday, March 27, 2012

Using NHibernate and related libraries in commercial projects

I like open source and try to take part in OSS ecosystem by supporting several own projects and contributing patches and fixes in projects which I used in my work. However if you live in enterprise development world you should care about licensing questions and before to decide which library or component you will use for proprietary software we should check does it contain compatible license.

Some time ago I made investigated licensing of NHibernate and it’s related projects which reveal all power of this ORM and make it more effective and productive: Fluent NHibernate and Linq 2 NHibernate. I decided to put this information into my blog because I know how licensing issues may be boring for developers and this info may also be useful for other people.

First of all about versioning. At the moment when I checked it (end of 2011) the following versions were available:

Fluent NHibernate 1.1 depends on NHibernate 2.1.2.4000
Linq 2 NHibernate 1.0.0.4000 -> NHibernate 2.1.0.4000
Linq 2 NHibernate 1.1.0.1001 -> NHibernate 2.1.2.4000

(There was lack of support of NHibernate 3.x in related projects). You may use other versions and use assembly binding redirection of course. In the table above I showed versions which were used for compilation of particular projects, i.e. if you will use them – you won’t have problems with compatibility. So it is possible e.g. to use NHibernate 2.1.2.4000, Fluent NHibernate 1.1 and Linq 2 NHibernate 1.1.0.1001 in the project without additional configurations. Now about licensing.

Library License
NHibernate GNU Lesser General Public License. This license allows to use library both in OSS and in commercial software.
Fluent NHibernate BSD license. It also allows to use it in commercial software. Plus according to the license text from project repository you should provide its license.txt with your application.

Linq 2 NHibernate

It is part of NHibernate contrib project which is also under LGPL, which allows to use it in commercial software.

So as summary you may use NHibernate and 2 mentioned projects in commercial software, except the cases when there are special conditions from the customers which may prevent to use that.

Saturday, March 24, 2012

Use App_LocalResources in visual web parts

In this post I will describe how to use standard ASP.Net technique for adding localized literals to the UI. When you create visual web part in VS 2010 Sharepoint project several files are added to the project:

  • Web part class
  • User control layout file (ascx)
  • Codebehind for user control
  • Sharepoint files for provisioning web part to site collection

image

If you will package project to wsp file and check structure inside it (you can also check it inside pkg subfolder in your project folder), you will find that ascx file will be copied to 14/ControlTemplates sub folder: in our example it is 14/ControlTemplates/SharePointProject1/VisualWebPart1. This is exactly the path which is used in LoadControl() method in web part class:

   1: public class VisualWebPart1 : WebPart
   2: {
   3:     private const string _ascxPath =
   4:         @"~/_CONTROLTEMPLATES/SharePointProject1/VisualWebPart1/VisualWebPart1UserControl.ascx";
   5:  
   6:     protected override void CreateChildControls()
   7:     {
   8:         Control control = Page.LoadControl(_ascxPath);
   9:         Controls.Add(control);
  10:     }
  11: }

In order to use standard ASP.Net local resources in ascx file we need to create App_LocalResources sub folder in the 14/ControlTemplates/SharePointProject1/VisualWebPart1 folder and put resx file with the following name: VisualWebPart1UserControl.ascx.resx. After this we can add localized literals into user control like this using standard ASP.Net convention:

   1: <asp:Label runat="server" Text="<%$Resources:Title%>" />

How to add App_LocalResources to the VS Sharepoint 2010 project in order to copy it to the VisualWebPart1 folder after provisioning? Of course we can add Sharepoint mapped folder to the project manually, then create VisualWebPart1 folder in it and put App_LocalResources there:

image

When we will package our wsp VS will merge user control and App_LocalResources into single folder. However the problem with this approach is that files are spread across project: web parts files are stored in one place while resx files for web parts in another.

VS allows to add local resources directly into visual web part project item:

image

After that you need to make extra step: in the properties of resx file set Deployment Type to TemplateFile and change Path under Deployment Location to the folder of your control (in our example it is ControlTemplates/SharePointProject1/VisualWebPart1/App_LocalResources):

image

With this approach all related files will be located in the same place in the project and will be copied to the correct folder during provisioning.

Sunday, March 11, 2012

Reason of type mismatch exception when working with SPWeb property bag

Some time ago I faced with unclear problem: when I tried to add value to the SPWeb property bag like this:

   1: CheckBox chk1 = ...;
   2: CheckBox chk2 = ...;
   3: SPWeb web = ...;
   4:  
   5: web.SetProperty("Foo", chk1.Checked);
   6: web.SetProperty("Bar", chk2.Checked);
   7:  
   8: web.Update();

SetProperty method is just wrapper over AllProperties collection which may be more familiar to developers:

   1: public void SetProperty(object key, object value)
   2: {
   3:     this.AllProperties[key] = value;
   4: }

As you can see in the code above boolean values are added to the property bag. Surprisingly this code caused type mismatch exception during web.Update() call:

Specified data type does not match the current data type of the property.
   at Microsoft.SharePoint.Utilities.SPUtility.UpdateArrayFromHashtable(Object& o, Hashtable ht)
   at Microsoft.SharePoint.SPWeb.Update()

What was even more interesting, is that only one boolean value caused this error (e.g. “Foo” on the example above), i.e. when I commented this single line, code became working having other boolean variables stored into the property bag.

I checked SPUtility.UpdateArrayFromHashtable() method in reflector. It is not the clearest method I saw in my experience. Let’s try to investigate what it actually does. According to its name it updates array of properties (2 dimensional) from Hashtable which stores local copies, i.e. properties which are not saved to content database yet. Lets check how it is called from SPWeb.Update() method:

   1: public void Update()
   2: {
   3:     ...
   4:     if (this.m_htProperties != null)
   5:     {
   6:         ...
   7:         SPUtility.UpdateArrayFromHashtable(ref this.m_PropertyArray, this.m_htProperties);
   8:         ...
   9:     }
  10:     ...
  11: }

Type mismatch exception is thrown from the following part of the UpdateArrayFromHashtable() method:

   1: internal static void UpdateArrayFromHashtable(ref object o, Hashtable ht)
   2: {
   3:     // ...
   4:     IDictionaryEnumerator enumerator = ht.GetEnumerator();
   5:     while (enumerator.MoveNext())
   6:     {
   7:         int num5;
   8:         // ...
   9:         if (hashtable.ContainsKey(str))
  10:         {
  11:             num5 = (int) objArray[2, (int) hashtable[str]];
  12:             hashtable.Remove(str);
  13:         }
  14:         else if ((enumerator.Value != null) && (enumerator.Value is string))
  15:         {
  16:             num5 = 1;
  17:         }
  18:         else
  19:         {
  20:             num5 = 0;
  21:         }
  22:         object obj3 = enumerator.Value;
  23:         if (obj3 != null)
  24:         {
  25:             Type type = obj3.GetType();
  26:             // ...
  27:  
  28:             if (((num5 > 0) && (num5 < ObjectType.Length)) &&
  29:                 (ObjectType[num5] != type))
  30:             {
  31:                 int num7;
  32:                 if (((type != typeof(string)) ||
  33:                     (ObjectType[num5] != typeof(int))) ||
  34:                     !int.TryParse(obj3.ToString(), out num7))
  35:                 {
  36:                     if ((type != typeof(int)) ||
  37:                         (ObjectType[num5] != typeof(string)))
  38:                     {
  39:                         throw new SPInvalidPropertyException(
  40: SPResource.GetString("InvalidPropertyType", new object[] { str }), str);
  41:                     }
  42:                     // ...
  43:                 }
  44:                 // ...
  45:             }
  46:     // ...
  47: }

ObjectType has Type[] type (i.e. array of types) which is initialized in static constructor of SPUtility class:

   1: Type[] typeArray = new Type[4];
   2: typeArray[1] = typeof(string);
   3: typeArray[2] = typeof(int);
   4: typeArray[3] = typeof(DateTime);
   5: ObjectType = typeArray;

As you can see “type” variable in the code above contains type of the current property (current loop step property) from Hashtable which as you remember contains list of local unsaved copies. I.e. in our case it contains bool type.

Another interesting variable is “num5”. It contains index in ObjectType array (i.e. values from interval [0..3]) for the property type. Important that if Hashtable contains property with the same name which already exists in the properties array, num5 will contain index for type of this existing property. Check lines 9-13 above:

   1: if (hashtable.ContainsKey(str))
   2: {
   3:     num5 = (int) objArray[2, (int) hashtable[str]];
   4:     hashtable.Remove(str);
   5: }

(hastable variable here is just converted array of existing properties). Let’s return to the code which throws exception. Lines 28-29 shows us that num5 contains value 1,2 or 3 (i.e. that previous type of property was string, int or DateTime) and that current type is not the same which was before update:

   1: if (((num5 > 0) && (num5 < ObjectType.Length)) &&
   2:     (ObjectType[num5] != type))
   3: {
   4:     ...
   5: }

But how property with the same name but different type came to the property bag? When I asked colleagues about it I found that previously for workaround of another problem this property was added to the property bag via PowerShell:

   1: $site =  new-Object Microsoft.SharePoint.SPSite($url)
   2: $web = $site.OpenWeb()
   3: $web.AllProperties["Foo"] = "True"
   4: $web.Update()
   5: $web.Dispose()
   6: $site.Dispose()

As you can see it was added using string. Code which reads this property may work both with strings and booleans (property bag returns instance of object type), that’s why it was not mentioned before. But when we tried to update it using boolean value it failed with type mismatch exception.

In order to fix it, just remove old property from the property bag:

   1: web.AllProperties.Remove("Foo");
   2: web.Update();

After that code which adds boolean values into property bag will work.

Friday, March 2, 2012

One problem with updating alert template for Sharepoint list

In this post I would like to describe one problem with Sharepoint alerts. Alerting is OTB Sharepoint feature which can be used for subscribing on the events from list or document library. Supported event types are:

  • All changes
  • New items are added
  • Existing items are modified
  • Items are deleted

Each list has single associated alert template. Programmatically you can read and set this alert template using SPList.AlertTemplate property. Each list created using OTB list template (e.g. Custom list) has associated OTB alert template. Alert templates are located in 14/Template/Xml/AlertTemplates.xml file. E.g. for Custom list there is “SPAlertTemplateType.GenericList” alert template. Alert templates contain subject and body which will be sent to end user.

You can modify existing alert templates or create new one. Most of the articles which I found say that you should not make changes in original AlertTemplates.xml (this is quite reasonable because it may be overriden by next Sharepoint update). Instead create a copy of this file, modify it (add new alert template with unique name or update existing template) and call updatealerttemplates stsadm command:

   1: stsadm -o updatealerttemplates -url http://example.com -f MyAlerttemplates.xml

However it is not the only way to create alerts templates. You can also create alert templates in configuration database. I will write separate post about it.

But let’s return to the original topic: suppose that you created new custom list. After creation it will have SPAlertTemplateType.GenericList alert template associated. Then users subscribe to events from this list or you subscribe them by yourself (it is possible to specify different users in the OTB alerts subscribe page). After that you change alert template, e.g. programmatically:

   1: list.AlertTemplate = alertTemplate;
   2: list.Update();

or using 3rd party component via UI. You may expect that users who subscribed to the list previously will receive emails created with updated alert template. But this is not the case. Previously subscribed users will receive emails created with old alert template, but those users who will subscribe after modification of alert template – will receive new emails.

It may cause many problems and a lot of debugging efforts. E.g. if you created custom alert handler (inheritor of IAlertNotifyHandler interface), attach it to some alert template (here is the good example of how to do it: How To: Customizing alert emails using IAlertNotifyHandler. It also uses example with file, not with config database), then associate alert template with list – but it is not fired anyway. One of the reason is that users were subscribed before alert template was changed for this list.

One way to avoid it is to set alert template for all subscribers when list alert template was changed. Here is code for this (didn’t test it by myself, but it should work):

   1: list.AlertTemplate = at;
   2: list.Update();
   3:  
   4: foreach (SPAlert alert in list.ParentWeb.Alerts)
   5: {
   6:     if (alert.ListID == list.ID)
   7:     {
   8:         alert.AlertTemplate = list.AlertTemplate;
   9:         alert.Update();
  10:     }
  11: }

or re-subscribe users manually. Hope this information will be useful for you if you will face with similar problem.