Prash's Blog

SharePoint SPItemEventReceiver ItemUpdating and ItemUpdated being called multiple times July 30, 2009

Filed under: SharePoint — prazjain @ 5:04 pm
Tags:

Problem : SPItemEventReceiver’s ItemUpdating and ItemUpdated methods are called twice! (to be precise)

And if you have Update() or SystemUpdate() call on the document / item, this leads to getting an exception message on screen about “XYZ has already been modified”. This happens due to race condition between the two threads when updating the document / item.

Background

In SharePoint, an event receiver can be hooked to a SPList to listen to any events occuring on the SPList.

This is how you do it :

SPWeb spWeb = GetSPWebInstance();
// assuming that you have a document library by name of "My List"
SPList sharedDocuments = web.Lists["My List"];
// attach a ItemUpdated event.
SPEventReceiverDefinition spEventReceiverDef = sharedDocuments.EventReceivers.Add();
spEventReceiverDef.Class = "Fully.Qualified.Class.Name";
spEventReceiverDef.Assembly = "AssemblyName, Version=x.x.x.x, Culture=neutral, PublicKeyToken=thePublicKeyToken"
spEventReceiverDef.Sequence = 5000; // put any number here
spEventReceiverDef.Type = SPEventReceiverType.ItemUpdated;
spEventReceiverDef.Update();

To attach an ItemUpdating event receiver, all you need to do it replace the type from SPEventReceiverType.ItemUpdated to SPEventReceiverType.ItemUpdating, and ofcourse the class / assembly information if you have the implementation in a different class / assembly.

ItemUpdating gets called synchronously just before an item is updated. And ItemUpdated gets called after the item is updated.

Cause

When force checkout is enabled on document library, this is how the document editing works :

When you checkout a document for editing, a local copy of the document is created that saves all the changes made by the user. At the time of checking-in the document, this local copy updates the original copy (even if there are no changes), then there is another request that does check-in on the document.

This is the reason why ItemUpdating and ItemUpdated are called twice (once for each step of check-in process).

Solution

A Property : “vti_sourcecontrolcheckedoutby” is provided that can be used to distinguish if the update is because of check-in or due to other reasons.

If this property exist in BeforeProperties and not in AfterProperties of SPItemEventProperties instance then it means that the callback is for item check-in event, else it is for event other than item check-in.

So if you have a piece of code that you want to be executed only when the check-in is done then enclose it in a conditional statement like this :

public override void ItemUpdated(SPItemEventProperties properties)
 {
// perform actions only if the update event is triggered by check-in
 if (properties.AfterProperties["vti_sourcecontrolcheckedoutby"] == null &&
 properties.BeforeProperties["vti_sourcecontrolcheckedoutby"] != null)
 {
// your code goes here
}
else
{
//ignore
}
}

Putting your code block in correct conditional section would make sure that your code gets executed for the check-in event callback.

 

Unable to preview / add SearchFacets webpart in SharePoint July 27, 2009

Filed under: SharePoint — prazjain @ 11:00 pm
Tags: ,

Recently when I tried to install the search facets webpart in my development environment, I was to find that it would not be as quick as I had thought it would be.

I followed all the steps in the guide but it would not allow me to preview the SearchFacets webpart (it allows me to preview the other two webparts). If I try to skip it, and go ahead with adding and using it, I just would not be able to add it!

I have raised this over here : http://facetedsearch.codeplex.com/Thread/View.aspx?ThreadId=62960&ANCHOR#Post216315 but still have to receive a reply that could possibly help me.

Apparently my colleague (whose VM I had copied over to my machine) was able to preview it and add it!

So given that we are both using the same VM copy, one is able to install and add it other one is not. Or maybe its some silly configuration I missed ?

In the mean time  while waiting for the reply, I have tried reinstalling my sql server and sharepoint server on the virtual machine. But it has not worked out yet.

In the worse case scenario, I would have tried creating a new VM image altogether and do it from scratch, but without knowing why it happened I cannot be sure it would not happen next time.

If anyone had faced the same problem and was able to resolve it, do post your comments about it.

 

Using Virtual Earth / Bing Geocoding Webservice July 24, 2009

Filed under: Bing — prazjain @ 4:10 pm
Tags: , , , ,

Me being new in London, I took it upon myself to find pubs close to me. I created a database with location of pubs, now I had to do display them on map to find their proximity. As I just had the address of these pubs and not their geographic coordinates, I needed to convert address to geographic coordinates. And then I can use them on any of the various mapping platforms to display location on map.

I wrote a tool which would enrich the existing database of pubs with their respective geocodes. I am giving an overview of what / how I went ahead with doing that:

  1. Get a windows live id, if you do not already have one. You can get one here : https://accountservices.passport.net
  2. Get a license for using the Geocoding Webservice. You would need to register for Bing Maps Developer account. You can sign up here : https://mappoint-css.live.com/mwssignup/
  3. Now, I am assuming you already have a Visual Studio solution and project created where you are going to use this functionality, add a reference to this web service :

    https://staging.common.virtualearth.net/find-30/common.asmx, use this as URL for service and name it as ‘VEStagingToken’.

  4. Create a class GeoCodeFetcher that would hold all the logic for interacting with geocoding webservices.
  5. In your project create a reference to webservice VEGeocodingService pointing to this url: http://staging.dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc?wsdl

PS : Bing limits you 30,000 transactions per day in the free developer account, which should be enough for any sample developer application. [Confirmed through offical sources and true as of July 2009]

For the rest, I have written commented code for your reading, this should be fairly self-explanatory.

Note: I could have refactored AddressToGeocode method a little bit to reduce the number of if/else conditions, but the aim of this post is to be make it more understandable for any new programmer who is using this service for the first time.

This Code sample is in C#, you can as well port this code to Java with minor syntax changes.


///<summary>
/// Responsible for getting geocodes for a pub address. This class also holds logic
///for connecting to databases and saving data back to databases.
///</summary>
public class GeoCodeFetcher
{
#region [ Private Variables ]
///<summary>
/// Token that is to be sent everytime a GeocodeService call is made to VE to get geocodes for
/// a pub address.
///</summary>
private string _clientToken;

#endregion

#region [ Constructors ]
///<summary>
/// Gets a token from VE webserice for making Geocode service calls.
///</summary>

public GeoCodeFetcher()
{
string veAccountId = "VEAccountId";
string veAccountPwd = "VEAccountPwd";
// create the token once for all calls
CreateToken(veAccountId, veAccountPwd);
}

#endregion

#region [ Private Methods ]

///<summary>
/// Get a token for invoking VE services with max validity of 60 mins.
///</summary>
///<param></param>
///<param></param>
private void CreateToken(string username, string pwd)
{

CommonService commonService = new CommonService();
commonService.Url = "https://staging.common.virtualearth.net/find-30/common.asmx";
commonService.Credentials = new System.Net.NetworkCredential(username, pwd);
// create token specification
TokenSpecification tokenSpec = new TokenSpecification();
// some ip, this is used for tracking by VE services. Ideally should be IP of machine that is
// making service calls. But it would not fail the call if its any valid ip.
// putting in some value as the service call should not fail because of this

tokenSpec.ClientIPAddress = "x.x.x.x";
tokenSpec.TokenValidityDurationMinutes = 60; // 8 hrs is max limit.
_clientToken = commonService.GetClientToken(tokenSpec);

}

#endregion

#region [ Public Methods ]
///<summary>
/// This gets the address as argument and returns the latitude and longitude geocode for it.
///</summary>
///<param></param>
///<param></param>
///<param></param>
public void AddressToGeocode(string address, out decimal latitude, out decimal longitude)
{
string results = string.Empty;
// create a geocode request
GeocodeRequest geocodeRequest = new GeocodeRequest();
//Set the token received from VE service as credential token
geocodeRequest.Credentials = new VEGeocodingService.Credentials();
geocodeRequest.Credentials.Token = _clientToken;
// set the address as query
geocodeRequest.Query = address;
// we want only the top result so min confidence is high

ConfidenceFilter[] filters = new ConfidenceFilter[1];

filters[0] = new ConfidenceFilter();
filters[0].MinimumConfidence = VEGeocodingService.Confidence.High;
//set the geocode options
GeocodeOptions geocodeOptions = new GeocodeOptions();
geocodeOptions.Filters = filters;
geocodeRequest.Options = geocodeOptions;
GeocodeServiceClient geocodeServiceClient = new GeocodeServiceClient();
GeocodeResponse geocodeResponse = geocodeServiceClient.Geocode(geocodeRequest);

// if more than one result available then take the top result
if (geocodeResponse.Results.Length > 0)
{
latitude = (decimal)geocodeResponse.Results[0].Locations[0].Latitude;
longitude = (decimal)geocodeResponse.Results[0].Locations[0].Longitude;
}
else
{
// try search with medium confidence
filters[0].MinimumConfidence = VEGeocodingService.Confidence.Medium;
GeocodeResponse gcrMedium = geocodeServiceClient.Geocode(geocodeRequest);
 if (gcrMedium.Results.Length > 0)
{
latitude = (decimal)gcrMedium.Results[0].Locations[0].Latitude;
longitude = (decimal)gcrMedium.Results[0].Locations[0].Longitude;
}
else
{
filters[0].MinimumConfidence = VEGeocodingService.Confidence.Low;
GeocodeResponse gcrLow = geocodeServiceClient.Geocode(geocodeRequest);
if (gcrLow.Results.Length > 0)
{
latitude = (decimal)gcrLow.Results[0].Locations[0].Latitude;
longitude = (decimal)gcrLow.Results[0].Locations[0].Longitude;
}
 else
{
// throw an exception that geocodes could not be found for this pub
throw new Exception();
}
}
}
}
#endregion
}

These are all the helper methods you would need to actually interact with the services, and use them in your code whereever you need to geocode data.

Check the article on how to use the geocoded values to calculate route from point A to point B and more. How to use Routing Service.

 

Sharepoint IdentityNotMappedException Some or all identity references could not be translated July 16, 2009

Filed under: SharePoint — prazjain @ 8:15 pm
Tags:

Error : Failed to create sample data

Exception : An exception of type System.Security.Principal.IdentityNotMappedException was thrown.  Additional exception information: Some or all identity references could not be translated.

How I got this error

Today while working on sharepoint to get Search working, I had a suspicion that my sharepoint installation might have got corrupted.

( Reason behind that is I had to rename my VM machine after installing sharepoint. As my VM was a copy of my colleague’s VM so we had same DB server names and other things which could mess things up.

So I followed the steps from one of the websites and it all seemed fine, until today when I could see some places where machine name / user names  etc were messed up because of which I could not get the search working in sharepoint.
)

So that meant either I revert back to a previous snapshot on my VM (and install all softwares I had installed since then ) or try installing Sql Server and Sharepoint again.

I chose the second option.

Both installed fine, but when it came to Configuration Wizard of Sharepoint, it failed at Task 8 : Failed to create sample data.

Exception

An exception of type System.Security.Principal.IdentityNotMappedException was thrown.  Additional exception information: Some or all identity references could not be translated.
System.Security.Principal.IdentityNotMappedException: Some or all identity references could not be translated.

Resolution

After googling it for some, I did these changes :

1) Remove all websites that had anything to do with sharepoint on my developer machine.

2) Remove application pools that had anything to do with sharepoint.

3) Added my current user LMACHINE\Administrator to my sql server 2005 instance as sys admin.

4) One of the application pools could not be deleted namely “Sharepoint – 80”, I investigated further and found that username was incorrect for this application pool. I corrected it and re-entered password.

Finally ran the Sharepoint Configuration wizard  again, and this time it worked !

What a relief.

The re-install of Sql Server 2005 and Sharepoint on my dev box, which according to estimates should have taken 30 mins, actually took 3 hours but how can you put that in a project plan.

 

Using SPFolder UniqueContentTypeOrder to Hide menu options July 15, 2009

Filed under: SharePoint — prazjain @ 5:41 pm
Tags: ,

I would explain the usage of this property by an example.
The example would be to hide a few items from the new menu button in menu bar.

I write a function that takes in a folder type and the list of content types that you want to hide from the

menu bar.

Now if you try to remove everything from the new menu button, you would get a runtime exception (just

like i got, so do not try that. Instead you might want to hide the new menu button!)

private void HideAllContentTypes(SPFolder rootFolder, IList<string> contentTypeNames)
 {
 IList<SPContentType> contentTypeCollection = rootFolder.ContentTypeOrder;

 List<SPContentType> contentTypes = new List<SPContentType>();

 foreach (SPContentType contentType in contentTypeCollection)
 {
 if (contentTypeNames.Contains(contentType.Name))
 continue;
 else
 contentTypes.Add(contentType);
 }

 rootFolder.UniqueContentTypeOrder = contentTypes;
 rootFolder.Update();
 }

Explanation of the code goes here :

1) IList<SPContentType> contentTypeCollection = rootFolder.ContentTypeOrder;
This step is important because you need to call SPFolder’s ContentTypeOrder property so that it initializes

internal storage structure and then when you later on call UniqueContentTypeOrder and set a collection on

it, you would not get a runtime exception.

2) Iterate through the SPContentType collection and create a new collection that does not include the

content types that you hate 🙂

3) Now you can assign SPFolder’s UniqueContentTypeOrder property with your new SPContentType

collection. Note you would get an runtime exception here if you tried to be smart and did not perform step 1.

4) Never forget to call Update() once you are done.

 

Defining Custom Folder content type and custom document content type July 14, 2009

Filed under: SharePoint — prazjain @ 10:49 pm
Tags: ,

A  Two Part Article Series

Part 1 : It would be about creating fields and custom content type for document and one

for folder. Then creating a feature to bind them together and activate on a site collection.

Part 2 : This would explain about you can associate those content types programmatically

with a document library.

Here we begin with Part 1

Note : Whereever you see Guids, replace them with new ones on your machine.

First we create a few fields that we can use in our custom content type

Create a file myfields.xml and paste this content in it :

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<!-- first two fields are for creating custom document content type -->
<Field Type="Text" DisplayName="File Checksum" Required="FALSE" MaxLength="255" 

Group="MyGroup" ID="{01837735-8dec-4f20-b725-5d05e8747f60}" SourceID="{89369214-f359-419c

-80de-14763a3fd415}" StaticName="FileChecksum" Name="FileChecksum"  ShowInNewForm="FALSE" 

ShowInEditForm="FALSE"  ShowInFileDlg="FALSE"></Field>
<Field Type="Note" DisplayName="Information" Required="FALSE" NumLines="6" RichText="TRUE" 

RichTextMode="Compatible" AppendOnly="TRUE" Sortable="FALSE" Group="MyGroup" 

ID="{f48d6644-70ff-402c-847f-728f388d19b7}" SourceID="{89369214-f359-419c-80de-

14763a3fd415}" StaticName="Information" Name="Information"></Field>
 <!-- folder status field used by Custom Folder -->
 <Field Type="Choice" DisplayName="Status" Required="TRUE" Format="Dropdown" 

FillInChoice="FALSE" Group="MyGroup" ID="{61CA18AF-307D-4e08-9907-0274ECABEE56}" 

SourceID="{89369214-f359-419c-80de-14763a3fd415}" StaticName="CustomFolderStatus" 

Name="CustomFolderStatus">
 <Default>Not Started</Default>
 <CHOICES>
 <CHOICE>Not Started</CHOICE>
 <CHOICE>In Progress</CHOICE>
 <CHOICE>Completed</CHOICE>
 </CHOICES>
 </Field>
</Elements>

Now we create another file ctypesCustomDocument.xml and paste this content in it :

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

 <ContentType ID="0x010100F2895C5AACCB5A4599ACEF15F4E082D3" Name="My Custom Doc Type" 

Group="MyGroup" Version="1">
 <FieldRefs>
 <FieldRef ID="{f48d6644-70ff-402c-847f-728f388d19b7}" Name="Information" 

Required="FALSE"/>
 <FieldRef ID="{01837735-8dec-4f20-b725-5d05e8747f60}" Name="File Checksum" 

Required="FALSE"/>
 </FieldRefs>
 </ContentType>
</Elements>

Then create a file ctypesCustomFolderType.xml and paste this content in it :

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

 <ContentType ID="0x01200051F2CB82D79099439C4C3AF20A5517B5" Name="My Custom Folder Type" 

Group="MyGroup" Version="0">
 <FieldRefs>
 <FieldRef ID="{61CA18AF-307D-4e08-9907-0274ECABEE56}" Name="Status" Required="TRUE"  

/>
 </FieldRefs>
 <XmlDocuments>
 <XmlDocument 

NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
 <FormTemplates 

xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
 <Display>ListForm</Display>
 <Edit>ListForm</Edit>
 <New>ListForm</New>
 </FormTemplates>
 </XmlDocument>
 </XmlDocuments>
 </ContentType>
</Elements>

So just to re-iterate what we have done till here is :
Create a custom document type called “My Custom Doc Type” that includes two field

“Information” and “File Checksum”.
Create a custom folder content type called “My Custom Folder Type” this includes one field

“Status” which is a choice field.

Now we create a feature.xml file and put references to these three xml files in that one

file.

Copy this xml in feature.xml file :

<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
 Id="52143C47-C82C-40af-873C-6C9E52E9C73D"
 Title="Custom Content Types"
 Description="Installs Custom Content Types"
 Version="1.0.0.0"
 Scope="Site"
 DefaultResourceFile="core"
 Hidden="false"
 >
 <ElementManifests>
 <ElementManifest Location="myfields.xml"/>
 <ElementManifest Location="ctypesCustomDocument.xml"/>
 <ElementManifest Location="ctypesCustomFolderType.xml" />
 </ElementManifests>
</Feature>

Now copy all the 4 files we have created till now into 12-

hive/TEMPLATE/FEATURES/CustomContentTypes

Install the content types using this command (you would have to add stsadm to your path

variable for this to work)

stsadm -o installfeature -name CustomContentTypes -force

Once it is installed, we see how it can be used in the next article of this series.

 

Conditionally elevating security privileges to approve custom content type document

Filed under: SharePoint — prazjain @ 9:48 pm
Tags: ,

In one of the posts before I had explained how to set content approval on a document library.

When you set content approval on that library, you would see that only when you are in admin / system account, that you can approve / reject documents else you cannot.

So here I would show you a scenario where you can elevate the privleges of the currently logged in user and approve / reject the document programmatically, depending on your business case.

Sample case : You have a Document Library “Custom List” which has documents of type “Custom Document”, now this “Custom Document” content type has some meta data associated with it i.e Document Type (which would be a kind of subtype to differentiate between different “Custom Document” files in the document library.
So based on this meta data / field of custom content type “Custom Document” we would decide if we want to automatically approve the document.

Here is the code that does the task :

1) To know how to enable content approval on a list programmatically, check the article here.

2) Now extend the class SPItemEventReceiver, to listen to the events the you are interested in handling.

3) Here we would capture the ItemUpdated event, and if the status of document is pending then elevate the privileges and approve the document.

public class CustomDocItemEventReceiver : SPItemEventReceiver
{
public override void ItemUpdated(SPItemEventProperties properties)
{
SPListItem item = properties.ListItem;
// if its a folder then return
if (item.Folder != null)
return;
// here you can have your code update the meta data of the document
//run with elevated rights
// store url of the list item in a variable as we would need to get a new instance of
// this list item in elevated privilege
string url = item.ParentList.ParentWeb.ServerRelativeUrl + "/" + item.Url;
// store the webGuid as we would need to get new instance of this web in elevate privilege
Guid webGuid = item.Web.ID;
// check if the document is in Pending state
if (item.ModerationInformation.Status == SPModerationStatusType.Pending)
{
// put a conditional logic that check if the list item meets the condition of being
// auto-approved.
bool approvalRequired = Helper.IsApprovalRequired(item);
if (!approvalRequired)
{
// disable event firing as you do not want recursive item updates to be invoked.
base.DisableEventFiring();
SPSecurity.RunWithElevatedPrivileges(delegate()
{
// important thing to keep in mind here is that you would need to get new instances to
// SPSite and SPWeb objects because the security privileges have changed. Just trying to
// get a reference to SPSite and SPWeb from objects that were instantiated in lower
// privileges would not work.
using (SPSite site = new SPSite(item.Web.Site.ID))
{
using (SPWeb web = site.OpenWeb(webGuid))
{
SPListItem elevatedItem = null;
elevatedItem = web.GetListItem(url);
elevatedItem.File.Approve("Updated by the system on major version increase");
}
}
});
// do not forget to enable the event firing again :)
base.EnableEventFiring();
}
}
}

}

This way a user with contribute permissions when updating the document can automatically approve the document.