SharePoint Internals – Hristo Pavlov’s Blog

30 May, 2008

Don’t Forget SPUtility

The SharePoint platform has huge capabilities. Because of this many of us building SharePoint customizations only have time to learn the basic SharePoint APIs which typically consist of how to work with objects such as sites, webs, list, files, features, solutions, workflows, event receivers, web parts and some others. Then often because of tight schedules we tend to start building our own helper methods on the top of the SharePoint core API instead of trying to find whether they are already implemented. And some of those custom built methods are actually already available in SharePoint.

This post is about one class which is very often forgotten or even unknown to many but which exposes some very useful methods. This is the Microsoft.SharePoint.Utility.SPUtility class and I want to show you some of its goodies.

Let’s start with these two:

public static string GetUrlDirectory(string url);

public static string GetUrlFileName(string url);

How many times you’ve had to manipulate URLs to get only the file name part or the folder path? I have always wondered why the URL versions of System.IO.Path.GetFileName() and System.IO.Path.GetDirectoryName() are not available in the .NET Framework. Well the above methods do exactly this and they work with both server relative and absolute URLs. It’s true that their implementation is very simple but why to do it yourself? Just remember those two the next time.

Another two routines that allow you to show a success or failure messages as a result of your operations are:

SPUtility.TransferToErrorPage(“Error from SPUtility!”);

SPUtility.TransferToSuccessPage(“Hello from SPUtility!”);

When you call them SharePoint will display an “Operation Completed Successfully” page or an “Error” page:

Again it is not a big deal but showing some neat messages like those only takes a single line of code. You can also specify the URL the browser will navigate to when the user clicks OK.

Sometimes you may need to open files from the 12 hive. Either your own or SharePoint configuration files or some other files. If you have been hardcoding the 12 hive path as C:\Program Files\Common Files….. then stop doing this now! The function below will return you the full path to a subfolder under the 12 hive.

public static string GetGenericSetupPath(string strSubdir);

for example calling GetGenericSetupPath(“Config”) will return the full path to the config folder under the 12 hive. Remember that the setup path may not necessarily be on the C drive and also in non english versions of Windows the program files folder is not called “Program Files” so start using this function.

A great function that I talked about in my What you need to know about AllowUnsafeUpdates (Part 2) post is:

public static bool ValidateFormDigest();

It will validate the form digest of your POST request and you will not have to worry about AllowUnsafeUpdates after that for newly created instances of SPSite.

And finally take some time to look at the other methods in the SPUtility class. You may find something else to be useful.

29 May, 2008

Customized Content Types and the SharePointInstaller

Filed under: SharePoint — Tags: , , , , — hristopavlov @ 1:43 am

If you are using the community SharePointInstaller to deploy your SharePoint solutions you should be aware that it will not overwrite customized content types and site columns.

So if you allow admins to customize your content types and site columns and you install a new version of your solution which has the same (or modified versions) of your site columns and content types, then the customized versions will not be overwritten by the ones from your new solution.

If you want to deploy your new versions on the top of the customized ones you will need to deactivate the features that install the content types/site columns using the -force switch of the deactivatefeature command. This will uninstall the customized versions. After that using the SharePointInstaller will work fine.

23 May, 2008

Why Should I Specify an ID for Dynamically Created HtmlControls in my WebParts

Filed under: ASP.NET, SharePoint — Tags: , , , , , — hristopavlov @ 2:24 am

What Is Happening:

Following the best practices for implementing WebParts we often create our controls dynamically in the CreateChildControls() method. However if your web part uses a ToolPart your server event handlers (such as OnClick handlers) may not be called.

When It Is Happening:

This problem happens with your dynamically created System.Web.UI.HtmlControls if you don’t specify the ID of the controls.

HtmlButton button = new HtmlButton();

tableCell.Controls.Add(button);

button.InnerText = “Create”;

button.ServerClick += new EventHandler(ButtonCreate_click);

The problem is only occurring if your web part implements a ToolPart.

Why It Is Happening:

What is happening is that, in the case there is a ToolPart involved, the CreateChildControls() comes before OnLoad(). So when we recreate our controls without giving them names they won’t have any name assigned until a later stage. At this later stage they will be given system generated names such as “ctl01“. However this later stage happens after the post back events (such as OnClick) are raised. When ASP.NET tries to call the event handlers it is not able to locate your controls because they don’t have an ID yet and no match can be found to the control that has generated the post back and is specified in the __EVENTTARGET  hidden field: 

private void RaisePostBackEvent(NameValueCollection postData)

{

    if (this._registeredControlThatRequireRaiseEvent != null)

    {

        this.RaisePostBackEvent(this._registeredControlThatRequireRaiseEvent, null);

    }

    else

    {

        stringstr = postData[“__EVENTTARGET”];

        bool flag = !string.IsNullOrEmpty(str);

 

        if (flag || (this.AutoPostBackControl != null))

        {

            Control control = null;

            if (flag)

            {

                control = this.FindControl(str);

            }

 

            if ((control != null) && (control.PostBackEventHandler != null))

            {

                stringeventArgument = postData[“__EVENTARGUMENT”];

                this.RaisePostBackEvent(control.PostBackEventHandler, eventArgument);

            }

        }

        else

        {

            this.Validate();

        }

    }

}

The problem with the different order of events was reported as well for example in the following post: http://www.schaeflein.net/blog/2004/03/29/OrderOfEventsInWebPart.aspx. I did a further experiment and found that this is exactly the reason for the problem. For example when I called manually base.Load() at the beginning of the CreateChildControls() everything was working fine even without explicitly giving a name to my controls.

How To Solve It:

Remember to always give names (ID) to your dynamically generated controls. This not only resolves this particular issue but also guarantees that system generated names will not cause same controls to have different IDs between different responses from the same page, which you don’t really want.

HtmlButton button = new HtmlButton();

tableCell.Controls.Add(button);

button.InnerText = “Create”;

button.ID = “btnCreate”;

button.ServerClick += new EventHandler(ButtonCreate_click);

 

21 May, 2008

What you need to know about AllowUnsafeUpdates (Part 2)

In short here is how to deal with AllowUnsafeUpdates:

1) Don’t update SharePoint objects from your code behind on GET requests as if you do so your code will be exploitable via a cross-site scripting. If you understand the consequences of doing this and still want to do it then see the first part of this article about how to use the AllowUnsafeUpdates property.

2) If your code is processing a POST request then make sure you call SPUtility.ValidateFormDigest() before you do anything else. This will ensure that the post request is validated (that it is not a cross-site scripting attack) and after that you will not have to worry about AllowUnsafeUpdates, because its default value will be “true” after the form digest is validated. To find out more about this read below.

So you have probably noticed from part one of the article that the internal method that sets the AllowUnsafeUpdates property had quite an interesting name: SetIgnoreCanary(). Canary is something that refers to a method of protecting from stack overflow attacks. The terminology is a reference to the historic practice of using canaries in coal mines, since they would be affected by toxic gases earlier than the miners, thus providing a biological warning system. In SharePoint the request canary is a unique pseudo-random value that protects you from cross-site scripting attacks. If you have ever examined the HTML source of your SharePoint pages you have probably noticed the __REQUESTDIGEST  hidden field.  This is what is referred to as the canary or the Form Digest and is used to verify that the request is genuine.

<input

    type=”hidden”

    name=”__REQUESTDIGEST”

    id=”__REQUESTDIGEST”

    value=”0x5DC31993EF285644A7C48F………..BFA2E6FB719CD7E9DB0922A329E97,19 May 2008 23:37:22 -0000″ />

As you see this is nothing more than a hidden field set by the server and verified back by the server when the page is submitted. As documented by Microsoft: The purpose of form digest validation is to help prevent security attacks where a user is tricked into posting data unknowingly to a server.

The place where the Form Digest value is set is the WebPartPage.FormOnLoad() method:

private void FormOnLoad(object sender, EventArgs e)

{

    if (HttpContext.Current != null)

    {

        SPWebcontextWeb = SPControl.GetContextWeb(HttpContext.Current);

        if(contextWeb != null)

        {

            SPWebPartManager.RegisterOWSScript(this, contextWeb);

            if (this.Page.Items[“FormDigestRegistered”] == null)

            {

                stringbstrUrl = SPGlobal.GetVTIRequestUrl(this.Context.Request, null).ToString();

                SPStringCallback pFormCallback = new SPStringCallback();

                contextWeb.Request.RenderFormDigest(bstrUrl, pFormCallback);

                base.ClientScript.RegisterHiddenField(“__REQUESTDIGEST”, SPHttpUtility.NoEncode(pFormCallback.StringResult));

                FormDigest.RegisterDigestUpdateClientScriptBlockIfNeeded(this);

                this.Page.Items[“FormDigestRegistered”] = true;

            }

        }

    }

}

 

The actual value of the __REQUESTDIGEST  field is generated by the COM objects in the OWSSVR.dll. After that another method is called: FormDigest.RegisterDigestUpdateClientScriptBlockIfNeeded()

public static void RegisterDigestUpdateClientScriptBlockIfNeeded(Page page)

{

    doubletotalMilliseconds;

 

    if (SPContext.Current.Site.WebApplication.FormDigestSettings.Enabled)

    {

        totalMilliseconds = SPContext.Current.Site.WebApplication.FormDigestSettings.Timeout.TotalMilliseconds;

        if(totalMilliseconds > 2147483647.0)

        {

            totalMilliseconds = 2147483647.0;

        }

    }

    else

    {

        return;

    }

 

    int num2 = Convert.ToInt32((double)(totalMilliseconds * 0.8));

    if(!page.ClientScript.IsOnSubmitStatementRegistered(typeof(FormDigest), “SPFormDigestUpdaterSubmitHandler”))

    {

      page.ClientScript.RegisterOnSubmitStatement(

            typeof(FormDigest),

            “SPFormDigestUpdaterSubmitHandler”,

            “UpdateFormDigest(‘” + SPEncode.ScriptEncode(SPContext.Current.Web.ServerRelativeUrl) + “‘, “+ num2.ToString(CultureInfo.InvariantCulture) + “);”);

 

        ScriptLink.Register(page, “init.js”, true, false);

    }

} 

There are a couple of interesting pieces of information in the code above. Firstly the Form Digest value generated by the server can expire. By default this happens in 30 min. Secondly the SPWebApplication.FormDigestSettings property can be used to change the form digest settings per web application. Those settings are persisted in the configuration database if you call SPWebApplication.Update(). The information provided in MSDN for the “Enabled” property is however not completely correct. MSDN says that: Enabled gets or sets a value that determines whether security validation is included with all form pages. But my SharePoint code examination and code tests showed that the Form Digest will be always included regardless of the Enabled value. The value of “false” means that when the digest expires (by default in 30 min) the user will not be able to submit the form and will get a security validation timeout exception trying to do so. Further test however showed that setting Enabled to false will indeed disable the security validation and you will not be getting the “The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.” exception even that AllowUnsafeUpdates will have a value of false.

Looking into the code of FormDigest.RegisterDigestUpdateClientScriptBlockIfNeeded() we see that it registers a client script that calls the UpdateFormDigest()  JavaScript function when the form is submitted. This JavaScript function calls the GetUpdatedFormDigest() method of the _vti_bin/sites.asmx WebService and updates the form digest field value on the fly before the form is submitted back to the server. According to the preliminary documentation of the Sites Web Service Protocol released by Microsoft on 4 Apr 2008: The GetUpdatedFormDigest is used to request renewal of an expired security validation, also known as a message digest. The purpose of form digest validation is to help prevent security attacks where a user is tricked into posting data unknowingly to a server.  To generate the new digest the web service simply creates a new instance of the FormDigest control and returns its DigestValue property value. So if the function is not called and the Form Digest is not updated a security timeout exception will occur and users will have to refresh a page before they can submitt it.

So the next question is where is the Form Digest validated? This is actually done in the SPWeb.ValidateFormDigest() method:

public bool ValidateFormDigest()

{

    HttpContext current = HttpContext.Current;

 

    if (current != null)

    {

        if (HttpContext.Current.Items[“FormDigestValidated”] == null)

        {

            if (!this.Request.ValidateFormDigest(this.Url, null))

            {

                return false;

            }

 

            current.Items[“FormDigestValidated”] = true;

 

            return true;

        }

 

        return true;

    }

 

    return true;

}

And as the code above shows the validation is done only once per web request and is then cached in the HTTPContext. Furthermore when a new SPWeb or SPSite object is created they also create an internal SPRequest object from the HTTPRequest. And within the lifetime of a single HTTP request to your web part or a web page, if the Form Digest has been successfully validated once then the AllowUnsafeUpdates property will now have a default value of true for all freshly created objects and the updates will be considered safe by SharePoint which also means you don’t have to set AllowUnsafeUpdates to do your job.

For some reason sometimes SharePoint doesn’t always call the ValidateFormDigest() method and this is why a workaround with setting AllowUnsafeUpdates to true is used. But a much better and safer solution is if you call this method yourself. The best way to do it is to call the SPUtility.ValidateFormDigest() method somewhere at the beginning of your POST request code behind.

And finally if you use in SharePoint you custom built ASPX pages which don’t inherit from a WebPartPage you can insert a FormDigest control in them which will insert the Form Digest  for you automatically and will protect you from cross-site scripting. Just make sure to call ValidateFormDigest() and DON’T touch the AllowUnsafeUpdates.

I hope that my investigation was more useful than confusing to you. Knowing more about how things work is always a key to building better and more secure applications.

Happy coding.

 

16 May, 2008

What you need to know about AllowUnsafeUpdates (Part 1)

In short here is how to deal with AllowUnsafeUpdates:

1) Don’t update SharePoint objects from your code behind on GET requests as if you do so your code will be exploitable via a cross-site scripting. If you understand the consequences of doing this and still want to do it then read below about how to use the AllowUnsafeUpdates property.

2) If your code is processing a POST request then make sure you call SPUtility.ValidateFormDigest() before you do anything else. This will ensure that the post request is validated (that it is not a cross-site scripting attack) and after that you will not have to worry about AllowUnsafeUpdates, because its default value will be “true” after the form digest is validated. To find out more about this read the second part of this article.

 

The Microsoft idea behind introducing the AllowUnsafeUpdates property is to protect YOU from cross-site scripting attacks. The way this works is that if your application is running in an HTTPContext (i.e. it’s a web part for instance) and the request is a GET request then SharePoint will refuse to do any changes unless the value of AllowUnsafeUpdates is set to true and by default it will be false for GET requests. If you try to do any updates to lists, webs or any SharePoint objects that require an SPSite to be created first, and if you don’t set AllowUnsafeUpdates to true you will get this exception:

System.Exception: Microsoft.SharePoint.SPException: The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again. —> System.Runtime.InteropServices.COMException (0x8102006D): The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.

It is important to understand that if you are writing a class library for example, your code will behave differently when called from a web application and when called from a rich client. Actually if the HTTPContext.Current is null then AllowSafeUpdates will be always true. This is the case in rich clients where no cross-scripting is possible as there are simply no web requests.

Usually when you create your own SPSite or SPWeb objects, i.e. when you are not getting them from the SPContext (such as SPContext.Web), and when you try to update anything such as web or list properties, list items metadata etc, you may get the exception listed above. This is a clear indication that AllowUnsafeUpdates of the SPWeb is false and this is preventing you from doing the update. This problem is resolved easily by setting the AllowUnsafeUpdates of the parent web object to true. Still sometimes even after you have done this you may still be getting the same error.  This is typically caused by one of the the following reasons:

A) You have set the AllowUnsafeUpdate to true for the wrong SPWeb

You have to be careful because sometimes the ParentWeb of an object is not the same instance of the web you have retrieved the object from. For example when you go initialWeb.Lists[listId] you would expect that the returned list’s ParentWeb instance is the same as you initialWeb. However this is not the case. So if somewhere later in your code you go list.ParentWeb.UpdateSomething() this will not work because you have never set the AllowUnsafeUpdates property of list.ParentWeb. You have set it for your initialWeb but even that this is the same web as the list’s parent web both are different instances. Usually you see the error and then you go and investigate in Reflector whether this is the same instance or not. Alternatively you could use another more generic and clever way to deal with almost any similar situation described in the following post:

http://community.bamboosolutions.com/blogs/bambooteamblog/archive/2008/05/15/when-allowunsafeupdates-doesn-t-work.aspx

The author suggests that you can set the HttpContent.Current to null before you do your updates and then reassign its initial preserved value when done. This will work great but remember to set the HTTPContent to null as early as possible. In the post above probably SharePoint uses the site.RootWeb to do the updates to the site scoped features and the RootWeb’s AllowUnsafeUpdates hasn’t been set to true explicitly.

B) The AllowUnsafeUpdates gets reset to false sometimes after you have set it to true

If we have a look at how the property is managed it turns out that it is stored in the request object associated with every SPWeb (which is actually a COM object)

[SharePointPermission(SecurityAction.Demand, UnsafeSaveOnGet = true)]

private void SetAllowUnsafeUpdates(bool allowUnsafeUpdates)

{

     this.Request.SetIgnoreCanary(allowUnsafeUpdates);

}

This actually means that every time the request is reset, the property will be also reset to its default value. The m_Request member is modified when a new web is created, when the web is disposed or when the SPWeb.Invalidate() method is called.

internal void Invalidate()

{

   if (this.m_Request != null)

   {

      if (this.m_RequestOwnedByThisWeb)

      {

         SPRequestManager.Release(this.m_Request);

      }

 

      this.m_Request = null;

   }

 

   this.m_bInited = false;

   this.m_bPublicPropertiesInited = false;

   this.m_Url = null;

}

So any operation that calls SPWeb.Invalidate() will reset AllowUnsafeUpdate to its default value. And for code running under HTTPContext, i.e. web applications, this default value for a GET request will be false. I’ve looked up for you all legitimate cases for which Invalidate() is being called by the SharePoint object model. These cases are:

1) When the Name or the ServerRelativeUrl properties of the SPWeb are changed and then Update() is called. In this case the AllowUnsafeUpdate is reset because with the change of these properties the URL of the web will change and logically the request object will change as it will now point to a different URL.

2) When any object that implements ISecurable (those are SPWeb, SPList and SPListItem) breaks or reverts their role definition inheritance. This means every time you call SPRoleDefinitionCollection.BreakInheritance(), BreakRoleInheritance(), ResetRoleInheritance() or set the value of HasUniquePerm the AllowUnsafeUpdates property of the parent web will reset to its default value and you may need to set it back to true in order to do further updates to the same objects.

3) In many cases when an exception is caught by the SharePoint object model when you try to retrieve any sort of data the AllowUnsafeUpdates of the parent web will be reset to false as a precaution to protect against potential exploits. In those cases however the objects will be in unknown state anyway after the request has been reset and the exception is re-thrown so they are of no practical interest.

And finally it is also good to mention that you may get another related exception when trying to update your SharePoint objects and that is:

System.Exception: Microsoft.SharePoint.SPException: Cannot complete this action.Please try again. —> System.Runtime.InteropServices.COMException (0x80004005): Cannot complete this action.

This usually happens when some updates have been made to an object (usually SPSite, SPWeb or SPList) that may be clashing with your changes and SharePoint refuses to do the update. To recover from this situation you simply need to create fresh copies of the SPSite and the SPWeb objects and do the updates on the objects retrieved from the fresh copies. And of course don’t forget to set the AllowUnsafeUpdates to true for the freshly created SPWeb if required.

15 May, 2008

Lookup Fields to the Same List – Defined as a Site Column

Filed under: SharePoint — Tags: , , , , , — hristopavlov @ 1:41 pm

Defining lookup fields that lookup in the same list can be quite useful. They are also easy to define when you add a field at the list level. Just select the type for the field to be “Lookup” and then for the “Get information from” list choose the same list as the one you are going to add the field into.

However you can’t do that if you want to define your field as a site column because the field is not added to any list at the time it is being defined and therefore your can’t lookup into a list that doesn’t exist yet. It would be useful though if somehow you could use some sort of “late binding” and choose the list later but when you are creating the site column from the UI you will have to pick up a concrete lookup list.

But there is antoher way. You indeed can define a reusable “late bindable” lookup field as a site column which will lookup into the same list as the list it is added to. How to achieve this is revealed if you look at the SPFieldLookup.LookupList property getter in Reflector:

public string get_LookupList()

{

    string fieldAttributeValue = base.GetFieldAttributeValue(“List”);

    if (fieldAttributeValue == “Self”)

    {

        while (base.ParentList != null)

        {

            return base.ParentList.ID.ToString(“B”);

        }

    }

    return fieldAttributeValue;

}

If you have defined lookup site columns using schema xml and SharePoint features you will know that the List attribute should be the ID of the list. What the code above tells you is that if you use the string “Self” as the value of the List attribute then this will indeed refer to the list itself.

So the solution is to deploy your field as a SharePoint feature and set its List attribute to “Self”. For example this is how SharePoint defines the “Related Issues” site column:

<Field ID={875FAB27-6E95-463b-A4A6-82544F1027FB}

       Name=RelatedIssues

       Group=$Resources:Extended_Columns

       Type=LookupMulti

       Mult=TRUE

       DisplayName=$Resources:core,Related_Issues;

       SourceID=http://schemas.microsoft.com/sharepoint/v3

       StaticName=RelatedIssues

       PrependId=TRUE

       List=Self

       ShowField=Title>

Interestingly there are two other special lookup lists, other than “Self”, which are “UserInfo” and “Docs”. “UserInfo” refers to the special peoples list SPWeb.SiteUserInfoList whlie “Docs” refers to the Docs table in SharePoint and allows you to lookup any of the database columns in the Docs table. This is what SharePoint does to retrieve the “Last Modified” column:

<Field ID={173f76c8-aebd-446a-9bc9-769a2bd2c18f}

       Name=Last_x0020_Modified

       SourceID=http://schemas.microsoft.com/sharepoint/v3

       StaticName=Last_x0020_Modified

       Group=_Hidden

       ReadOnly=TRUE

       Hidden=TRUE

       DisplayName=$Resources:core,Modified;

       Type=Lookup

       List=Docs

       FieldRef=ID

       ShowField=TimeLastModified

       Format=TRUE

       JoinColName=DoclibRowId

       JoinRowOrdinal=0

       JoinType=INNER>

14 May, 2008

Uploading a file & Event Receivers: The file … has been modified by …

Filed under: SharePoint — Tags: , , , , — hristopavlov @ 1:20 am

This error occurs when you upload a file to a document library through the SharePoint UI and when the document library has an event receiver which updates the list item on ItemAdded(). The error doesn’t happen all the time which lead us to conclude that it is due to some race conditions, especially knowing that event receivers are run in separate thread.

The error message is “The file … has been modified by … on ….” and the callstack leads to a COMException in the Microsoft.SharePoint.Library assembly, which is the managed wrapper of OWSSVR.dll. The full stacktrace looks something like this:

[COMException (0x81020037): The file Administration/test.doc has been modified by SHAREPOINT\system on 12 May 2008 15:14:29 +1000.]
at Microsoft.SharePoint.Library.SPRequestInternalClass.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish) +0
at Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish) +411

[SPException: The file Administration/test.doc has been modified by SHAREPOINT\system on 12 May 2008 15:14:29 +1000.]
at Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean   bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish) +556
at Microsoft.SharePoint.SPListItem.AddOrUpdateItem(Boolean bAdd, Boolean bSystem, Boolean bPreserveItemVersion, Boolean bNoVersion, Boolean bMigration, Boolean bPublish, Boolean bCheckOut, Boolean bCheckin, Guid newGuidOnAdd, Int32& ulID, Object& objAttachmentNames, Object& objAttachmentContents, Boolean suppressAfterEvents) +3030
at Microsoft.SharePoint.SPListItem.UpdateInternal(Boolean bSystem, Boolean bPreserveItemVersion, Guid newGuidOnAdd, Boolean bMigration, Boolean bPublish, Boolean bNoVersion, Boolean bCheckOut, Boolean bCheckin, Boolean suppressAfterEvents) +632
at Microsoft.SharePoint.SPListItem.UpdateOverwriteVersion() +190
at Microsoft.SharePoint.WebControls.SaveButton.SaveItem(SPContext itemContext, Boolean uploadMode, String checkInComment) +256
at Microsoft.SharePoint.WebControls.SaveButton.SaveItem() +111
at Microsoft.SharePoint.WebControls.SaveButton.OnBubbleEvent(Object source, EventArgs e) +476
at System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +50
at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +39
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3215

I spent some time writing test code to try and reproduce the problem when multiple threads are doing updates to the same file and I finally managed to get the error. Then I isolates the cause and reworked the code so the exception is thrown even from a single thread. I finally ended up with the code below which always causes the error. It is running in a single thread but it represents exactly what is happening with the event receiver and the EditForm.aspx pages if you think of it this way: All objects with index “1” are accessed from the event receiver and all objects with index “2”are accessed from the EditForm.aspx code-behind.

using (SPSite site1 = new SPSite(http://server/sites/test/&#8221;))

using (SPSite site2 = new SPSite(http://server/sites/test/&#8221;))

{

      SPFilefile1 = site1.OpenWeb().Lists[“ListName”].RootFolder.Files[0];

      SPFilefile2 = site2.OpenWeb().Lists[“ListName”].RootFolder.Files[0];

 

      // It is important to read a field value

      object readValue2 = file2.Item[“FieldName”];

 

      file1.Item.UpdateOverwriteVersion();

 

 

     // The next line throws exception

      file2.Item.UpdateOverwriteVersion();

}

The issue only happens when the code is executed in the exact sequence listed above. There is no exception thrown if we first update item2 and then item1. It doesn’t happen if we read a value from item1 rather than item2 and it doesn’t happen if we don’t read any value at all. It also happens no matter if we are using one or two different SPSite objects. All this explains why the issue is not happening all the time but only when specific race conditions are met.

The next question is of course how do we prevent the problem from happening. Well we cannot change  the SharePoint code so we have to find a way to update our event receiver code (which is the one with index “1” above). Also note that the EditForm.aspx uses exactly the SPListItem.UpdateOverwriteVersion()method to do the item metadata update and this cannot be changed either.

There are four ways to uppdate a list item through the object model. All of them call the internal method UpdateInternal() which has some interesting arguments like bSystembPreserveItemVersion and bNoVersion. The table below shows the different ways to update a list item and how they call the UpdateInternal() method:

Method   bSystem     bPreserveItemVersion    bNoVersion
SystemUpdate() true false false
SystemUpdate(false) true true false
UpdateOverwriteVersion() false false true
Update() false false false

The experiment showed that all of them but one were leading to an exception being thrown. The method that worked was SystemUpdate(false).

So one way to fix the problem is changing your event receiver to update the list item using SystemUpdate(false). Some people are additionally recommending to disable the event firing when calling the update as well:

this.DisableEventFiring();

try

{

      itemToUpdate.SystemUpdate(false);

}

finally

{

      this.EnableEventFiring();

}

This will work well unless you actually want to create a new version of the list item or call any of the other update methods for whateever reason. If that’s your case you could try to use a workaround of delaying the event handler for a couple of seconds (using Thread.Sleep() ) which of course is far from an elegant solution or you could see whether you can move your code to ItemUpdated() or ItemUpdating() rather than ItemAdded().

If you are getting the error in a different case i.e. when your custom code is where the error is thrown (in the case described here the error is thrown in the SharePoint code) you could implement a workaround of waiting and retrying your action again and again after a few seconds for a couple times. As an implementation you could use a try – catch block with a goto statement that moves the execution before the try. This is a very legitimate and good example of when using a “goto” statement in your code is actually good. If you don’t agree with me about the “goto” statement then look at this different but similar problem caused by race conditions and how it is solved using a try-catch-goto block in this Microsoft article: http://msdn.microsoft.com/en-us/library/cc303696.aspx

Usually if you are getting the error in your own code it is due to an SPSite created in a completely different branch of the solution and then SPWebs or SPLists are being passed as arguments down to your function where you try to update the list item. To resolve this error always remember to create a new fresh SPSite object to retrieve the list and the file objects from, in the same method where you are doing the update. This helps big time and solves 90% of the causes of this error I’ve seen. If needed you can additionally add more advanced thread synchronisation techniques to your code such as lock statements, mutexes, semaphors etc.

12 May, 2008

Defining Custom Properies of SharePoint Fields

Filed under: SharePoint — Tags: , , — hristopavlov @ 12:17 am

One good thing about the fields schema xml is that SharePoint will not complain if you define your own attributes … well almost not complain.

So this means you can extend the fields definition and add your own properties of the fields which gives a great power to your SharePoint solutions.

 

      <Field ID={b3cae114-0197-4599-96de-c21aa709c25f}

            Type=Text

            Group=My Solution Group

            Name=BusinessUnitName

            DisplayName=Business Unit Name

            StaticName=BusinessUnitName”

            ShowInEditForm=FALSE

            ShowInNewForm=FALSE

            Hidden=FALSE

            Required=FALSE

            Sealed=FALSE

            my:MyCustomValue1=FALSE

            my:MyCustomValue2=value/>

 

The only important bit is to use a custom namespace prefix for your custom attributes as if you don’t do so your feature will fail the schema validation when you try to activate it. So simply define your namespace in the Elements element.

<Elements

xmlns=http://schemas.microsoft.com/sharepoint/ xmlns:my=http://schemas.mycompany.com/sharepoint/my/>

 

And finally to read the values of the custom attributes of your fields you will have to go via the SPField.SchemaXml property, parse the XML and read the value.

I will leave it to your imagination to think about all the powerful things you can implement by extending your field attributes.

1 May, 2008

A duplicate name “Item” was found

Filed under: SharePoint — hristopavlov @ 7:29 am

This is another strange error I got when trying to create a site or a site collection. At the time I wrote this there was only one post about this error:

http://www.eggheadcafe.com/software/aspnet/30673124/a-duplicate-name-item-w.aspx

I was experiencing exactly the same symptoms as described in the above post. However the case there was due to forgotten two zeroes between the system content type you are inheriting from and your GUID.

However this was not my case. After an hour or more investigation I found that some of the IDs of our content types were recently updated to have a different parent. However one of them was missed out so we were having an id such as:

 

<ContentTypeID=0x0101007D9DECFFC8C2403B8A74F65174E2557502” …

or if I split it

0x0101 + 00 + GUID-OF-THE-PARENT-CONTENT-TYPE + 02

That is the id of the “Item” content type + the two zeroes you shouldn’t forget (which if you do will lead to the same error message) + the GUID of the parent content type + the unique part of this child content type (02 in this case).

So the problem was that the parent content type didn’t exist anymore and after updating the GUID with the new one everything was working again.

Hope this could be useful to someone.

Blog at WordPress.com.