Pages

Thursday, February 17, 2011

IE9 and User Agent String

With new IE in market the web developer community has been in the danger of supporting another extra browser. Recently we have found our application is in problem with IE9. We have a client application that put some data in User Agent Post platform value in registry. From IE6 to IE8 the post platform value was passing from server to client in UserAgent. So basically we had put some value in UserAgent Post Platform in registry from a client application and from our web application we had found the value (as the post platform values are passed in User Agent from IE6 to IE8). But with IE the post platform values are not passed with User Agent automatically.

 

User Agent and Pre/Post Platform value

If you are interested on how the Pre and Post Platform values works with User Agent you can visit the MSDN link: http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx

 

IE9 introduces Default User Agent and Extended User Agent

In IE9 the user agent has been divided into two parts:

Default User Agent (UA): IE9 by default will not send the pre/post platform values from client to server. So if you have some values in client’s pre and post platform then your web site will not get those values (from pre and post platform) as IE9 will not pass those values to server. However IE6 to IE8 will work as usual (i.e., pass those pre and post platform values) .

Extended User Agent (UA): So the question is if IE9 doesn’t send the pre and post platform values by its own then how can we access the platform values? The answer to this question is simple but implementation is tricky. You can get the pre and post platform values on client side only with JavaScript by accessing Navigator.UserAgent. So IE9 will not take the responsibility of passing Extended UA from client to server. You need to do it by your own. You need to read the extended UA by javascript on the client side and need to pass to the server by using Hidden control or any other way.

 

How to get the Pre/Post platform vales IE9?

By following javascript code you can get the pre/post platform values on client side.

<script type="text/javascript">
    alert(navigator.userAgent);
</script>

You can get more details on this on MSDN IE blog: http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx

Wednesday, January 19, 2011

U2U CAML Query Builder for SharePoint 2010?

U2U CAML query builder is a great tool for SharePoint developer. I’ve used this tool frequently with SharePoint 2007 development. However, I’ve got into trouble with using the tool with SharePoint 2010. The tool is not updated for SharePoint 2010 yet. Though you can use U2U CAML builder with SharePoint 2010 (shown later in this post), you want to use new features of CAML (say join lists) and such new features are not supported in CAML builder. In this post I’ll show how you can generate CAML query for SharePoint 2010 using few tools and Linq query. First let’s discuss on how you can still use CAML builder to generate CAML for SharePoint 2010.

 

Use CAML Builder with SharePoint 2010 (I’ll not recommend)

Till now the CAML builder doesn’t support SharePoint 2010. However, you can make the tool working by using connecting via web service as shown below:

image

Figure 1: Use CAML builder with SharePoint 2010

 

However, you are not saved. As I mentioned already till the date I’m writing this post, the CAML builder is not updated to support SharePoint 2010. So new CAML features (like list joins) will not work in this old CAML builder.

 

Generate CAML from Linq-to-SharePoint (Better approach)

Though you can use old CAML builder with SharePoint 2010, since the CAML builder is not updated yet, you will not get the CAML new features when you will use CAML builder. Worried? Please don’t. There’s an way out. Let me explain

  1. Download CKS Visual Studio Extension: There’s an useful and handy Visual Studio Extension for SharePoint developers known as Community Kit for SharePoint (CKS): Development tool edition. You can download the extension for SharePoint Server or for SharePoint Foundation.

  2. Generate Entity classes from SharePoint site: Once you have installed the Community Kit for SharePoint, you can generate entity classes from Server Explorer. First open a SharePoint project in Visual Studio and then connect to the SharePoint server from Server Explorer ==> SharePoint Connections. Then right click on your web and click “Generate entity classes” as shown below. This will generate the entity classes in the current selected project of Visual Studio.

    image
    Figure 2: Generate entity class from SharePoint site/web


  3. Write Linq using Linq-to-SharePoint against generated entity classes: Once you have generated the entity classes as described on step2, you can use Linq to SharePoint to write your logic that you want to achieve through CAML. Once you write the Linq, you can run the code and log the CAML generated from the Linq. For example I have two lists Orders and Product. I want to join two lists to get order title and product name. The Linq will look like as shown below:

    using (var dataContext = new MySiteDataContext(siteUrl))
    {
        TextWriter textWriter = new StreamWriter(@"c:\caml.txt",false);
        dataContext.Log = textWriter;
        var result = from o in dataContext.Orders
                        join p in dataContext.Product on o.Product.Id equals p.Id
                        select new {OrderName = o.Title, p.ProductName};
        foreach (var v in result)
        {
            System.Console.WriteLine("{0}----{1}",v.OrderName,v.ProductName);
        }
    }
    Code snippet 1: Linq query using Linq to SharePoint


    As shown in the code snippet above, I have used the data context generated at step 2 and I have run a Linq query against two lists of the data context. The most import thing to notice in above code snippet is marked with yellow. I have instantiated a text writer (initialize with file stream) and then I had set it as the Log property of the datacontext. This will ensure that any CAML generated from the Linq query will be written in the writer. Once the query gets executed the CAML is dumped in the file (in my case C:\Caml.txt).

    So for generating any complex CAML query you can first write its equivalent Linq query and then get the CAML from log.


  4. Get the CAML from the Linq query: After running the Linq to SharePoint query, you have got the CAML query in the log file as shown in step 3. However, you need to work a bit to make the CAML usable in SPQuery. The CAML generated from code snippet 1 is shown below:


    <View>
      <Query>
        <Where>
          <And>
            <BeginsWith>
              <FieldRef Name="ContentTypeId" />
              <Value Type="ContentTypeId">0x0100</Value>
            </BeginsWith>
            <BeginsWith>
              <FieldRef Name="ProductContentTypeId" />
              <Value Type="Lookup">0x0100</Value>
            </BeginsWith>
          </And>
        </Where>
        <OrderBy Override="TRUE" />
      </Query>
      <ViewFields>
        <FieldRef Name="Title" />
        <FieldRef Name="ProductProductName" />
      </ViewFields>
      <ProjectedFields>
        <Field Name="ProductProductName" Type="Lookup" List="Product" ShowField="ProductName" />
        <Field Name="ProductContentTypeId" Type="Lookup" List="Product" ShowField="ContentTypeId" />
      </ProjectedFields>
      <Joins>
        <Join Type="INNER" ListAlias="Product">
          <Eq>
            <FieldRef Name="Product" RefType="ID" />
            <FieldRef List="Product" Name="ID" />
          </Eq>
        </Join>
      </Joins>
      <RowLimit Paged="TRUE">2147483647</RowLimit>
    </View>
    Code snippet 2: CAML generated from Linq query of Code Snippet 1

    In the above code snippet, the lines marked with yellow can be modified if you want. Specially the content type in where part is put in the CAML to ensure only list items are selected. FYI, Content type 0x0100 means list item type. 


  5. Use CAML query in SPQuery: Now you have got the CAML and you want to use the CAML in SPQuery. The following code snippet shows how I’ve used the CAML(from code snippet 2) in SPQuery:

    SPQuery query = new SPQuery();
    query.Query = @"<Where>
                        <And>
                        <BeginsWith>
                            <FieldRef Name='ContentTypeId' />
                            <Value Type='ContentTypeId'>0x0100</Value>
                        </BeginsWith>
                        <BeginsWith>
                            <FieldRef Name='ProductContentTypeId' />
                            <Value Type='Lookup'>0x0100</Value>
                        </BeginsWith>
                        </And>
                    </Where>
                    <OrderBy Override='TRUE' />";
    
    query.ViewFields = @"<FieldRef Name='Title' />
                        <FieldRef Name='ProductProductName' />";
    query.ProjectedFields = @"<Field Name='ProductProductName' Type='Lookup' 
                                     List='Product' ShowField='ProductName' />
                              <Field Name='ProductContentTypeId' Type='Lookup' 
                                     List='Product' ShowField='ContentTypeId' />";
    query.Joins = @"<Join Type='INNER' ListAlias='Product'>
                    <Eq>
                        <FieldRef Name='Product' RefType='ID' />
                        <FieldRef List='Product' Name='ID' />
                    </Eq>
                    </Join>";
    
    query.RowLimit = 2147483647;
    
    var list = web.Lists["Orders"];
    var items = list.GetItems(query);
    foreach (SPListItem item in items)
    {
        System.Console.WriteLine("{0}--{1}", item["Title"], item["ProductProductName"]);
    }
    Code Snippet 3: Using generated CAML in SPQuery

Integrating the Test Code in your Visual Studio Soltuion

You may need to generate the CAML from time to time in your development life cycle. My personal preference is to keep a devtest project in the Visual Studio solution to do work like this CAML generation. I keep a dev-test project (used for RnD like task), mainly console app, in the solution. So you can keep the dev-test project in your solution and in that project you can write the Linq to SharePoint query and generate the CAML. Since the project will always be in your Visual Studio solution, if you need to get the CAML anytime you can just write the Linq query in the dev-test project and run the project to get the CAML.



Conclusion

So here’s the summary on how to generate CAML query using the method described in this post:

  • Install Visual Studio extension - CKS for SharePoint Server or for SharePoint Foundation.
  • Generate entity classes from SharePoint site using the CKS feature
  • Write Linq to SharePoint query and log the CAML in a file/Console
  • Use the CAML in SPQuery

Though you can use Linq to SharePoint instead of CAML, but sometimes CAML is needed for raw query and this method will help you find out the CAML.

Thursday, January 13, 2011

SharePoint 2010: Configure Form Based Authentication (FBA)

I had worked with form based authentication in SharePoint 2007. However, in SharePoint 2010, there’s few changes in the way form based authentication works. In my another post “Form-Based Authentication with ADAM”, I had described how to implement ADAM form based authentication in SharePoint 2007. Today I’ll show you how you can implement Form Based authentication using Active Directory Lightweight Directory Service. From windows server 2008, ADAM is replaced by Active Directory Lightweight Directory Service and both are LDAP based.

 

FBA works for only for Claims based authentication sites

In SharePoint 2010 to use FBA, you need to create a web application with Claims based authentication as shown below. Form based authentication will not work for web application created with classical authentication.

image

Figure 1: Create web application in claims based authentication

 

If you don’t create the web application with Claims Based Authentication then you’ll find the Forms Authentication type disabled in Authentication Provider settings window as shown below:

image

Figure 2: Forms authentication is disabled for web application created with “Classic Mode Authentication”

 

Step 1: Create a web application with Claims Based Authentication

Since form based authentication doesn’t work with “Classical Mode Authentication”, you can’t configure form based authentication with web application created with “classic mode authentication”. So to configure Form based authentication you need to have an web application created with Claims based authentication. (If you want to use windows authentication now and have plan to use forms based authentication later, then the best will be to create the web application with Claims based authentication). FYI,

  • Creating a Claims based authentication will allow you to use both windows and form authentication.
  • Creating a site with classic authentication mode will not allow you to configure the site to use form authentication easily.

 

Summary: In this step we’ll create an web application using Claims Based Authentication but use windows authentication as shown below. Later we’ll configure the site to use form authentication.

image

Figure 3: Create Claims based authentication web application with only windows authentication enabled

 

Step 2: Add membership provider entries in web.config files

In this example I’m considering you have the member provider configured already. I’ve been used “Active Directory Lightweight Directory Service” to test this form authentication. You need to modify three different web.config files (your web application, central admin and STS config file). Modifications to the three files are adding two entries (providers, connectionstring) to web.config file which are described below for three different places:

Web Application’s web.config:

You need to put the following entries in the web.config file of your web application under Configuration node:

<connectionStrings>
    <add name="MyProviderConnectionString" 
         connectionString="LDAP://myserver/O=a,OU=b,C=c" />
</connectionStrings>

Code Snippet 1: Connection String to LDAP

 

Then find the <membership> node under <system.web> and add an entry for your provider (There should be an entry with name i, added by SharePoint already). As shown below I’ve added a provider “MyProvider” in the providers list. The provider with name “i" was already in the web.config file which is added by SharePoint when you create an web application with claims based authentication.

<membership defaultProvider="i">
  <providers>
    <add name="i" 
         type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthMembershipProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add name="MyProvider" 
         type="System.Web.Security.ActiveDirectoryMembershipProvider,
System.Web,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="MyProviderConnectionString" connectionUsername="CN=aa,CN=Admins,O=a,OU=b,C=c" connectionPassword="***" enableSearchMethods="true" connectionProtection="None" /> </providers> </membership>

Code Snippet 2: My custom provider (MyProvider) added alongside the default SharePoint provider (i).

 

SharePoint by default add the provider with name ‘i’. I’ve defined my provider with name MyProvider and the provider is using MyProviderConnectionString. so your web.config file will look like as shown below:

image

Figure 4: ConnectionString and Provider defined in web.config.

Security Token Service’s web.config file

You need to add the same entries for two other web.config files. One is central admin web.config file. Another one is Security Token Service (STS). You can find the STS web config file as shown below. The default location is “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\SecurityToken\Web.config”.

image

Figure 5: Security Token Service (STS) web.cofig from IIS (Content View)

 

After opening the config file, add the two entries as shown in code snippet 1 and code snippet 2.

Central Admin’s web.config file

Finally you need to open the web.config file of central administration and add the two entries shown in code snippet 1 and 2.

 

Step 3: Change the web application’s security settings to use Form Authentication

So in Step 2, you have added the provider information (connection string, provider name, username etc) in three different web.config files. Now you need to tell the web application to use Form based authentication by connecting through your provider defined in web.config file. To do so follow the steps below:

  1. Login to central administration site and click “Application Management” from left side navigation.
  2. Select your web application from web application list and click “Authentication Providers” from ribbon as shown below:

    image

    Figure 6: Configure Authentication Provider from Central Administration

  3. From the “Authentication providers” dialog click on the zone (Default, internet etc) you want to configure the form authentication and then you will be redirected to “Edit Authentication” page.
  4. In the Edit Authentication page, Put your provider name as shown below. You can enable both form and windows authentication if you want. As shown in the read in the image below, if you don’t configure windows authentication in any zone of the web application then crawling will be disabled. If you want you can enable anonymous login from this “Edit Authentication” page.

image

Figure 7: Enable Form authetication

 

Step 4: Assign/change site collection administrators for the site collections

As soon you change the authentication type to form, you will have to assign an user (from your provider defined in web config file) to the site collection administrators.

  1. Click Application Management ==> Change Site Collection Administrators
  2. Then add the users from your providers in site collection administrator's group as shown below:

image

Figure 8: Add/Edit Site collection administrators

If you enable both windows and form authentication then it’ll be better to use one site collection administrator from windows and another from your form based authentication’s provider.

 

You are done!

And if you have followed the steps, you are done. If you try to access the site, you will be prompted for form login page. However, if you enabled both form and windows authentication then you will prompted for authentication  type first and based on the authentication type either you will be prompted for form or windows authentication.

 

For Your Information

Few points to notice here:

  • Form based authentication only works for web application created with Claims Based Authentication mode.
  • You need to modify three web.config files (your web application, central web app and Security Token Service) to add your provider settings.
  • Recommendation is to create an web application with Claims based Authentication mode but using windows authentication. Once you modify those three web.config files, switch the web app to form based.
  • You can enable both windows and form authentication in a web application. In that case try to add one site collection administrator from form authentication provider and another from windows.
  • If you want the site data to be crawled, then you need to make sure at lease one zone in the web application uses windows authentication.

Wednesday, January 5, 2011

SharePoint 2010: Add favicon icon to site

In SharePoint if you need to add favicon, you can do so easily by using SharePoint Out-of-box control SPShortcutIcon. The following code snippet shows how you can set the favicon:asdf

<SharePoint:SPShortcutIcon runat=”serverIconUrl=”YourIconUrl/> 

You are done…

Monday, January 3, 2011

Awarded Microsoft MVP

The very good news for me that I’ve come to know on 1st January is that I’ve been awarded Microsoft MVP for SharePoint Server: Development. My blog has got much popularity in last few months. On November there were almost 10000 visitors and on December around 9000 visitors visited my blog. Thanks all the visitors of the site for their comments and feedback. I’ll continue my efforts to help SharePoint community with blogs, MSDN forms etc.

Thanks again to all visitors who commented and provided feedback in my blog.

Friday, December 31, 2010

SharePoint 2010: Taxonomy Event Receiver?

One of the power of SharePoint is extensibility. You can hook your custom code in different places in different time with Event Receiver. Taxonomy is new and powerful feature added in SharePoint 2010. Unfortunately, the taxonomy missing event receiver feature. If you want to do something when taxonomy added, deleted or updated, you are unlucky that SharePoint 2010 doesn’t provide event receiver in SharePoint 2010 for taxonomy.

 

Why taxonomy event receiver needed?

With taxonomy support, SharePoint is a good place to manage taxonomy. However, if I need this taxonomy to use in another application (like another asp.net application) then with the event receiver I could sync my asp.net application with SharePoint taxonomy. Also when a taxonomy is deleted I would like to run my own code to allow or disallow the deletion. So taxonomy event receiver would be very handy if it would be available.

 

How taxonomy managed in SharePoint?

Let’s discuss a bit about how taxonomy managed in SharePoint. Taxonomy is managed by central administration (with managed metadata service). So when you add/delete/update taxonomy, the taxonomies are managed in central administration site. However, for faster retrieval of taxonomies in individual web, a hidden list of taxonomies maintained in each site collection. So in each site collection, there’s a hidden list TaxonomyHiddenList that keeps the copy of taxonomies from central administration site. And in every hour a timer job “Taxonomy Update Scheduler” is run to sync the taxonomies between central admin and site collection. However, the hidden list in site collection doesn’t contain all taxonomies from central admin rather taxonomies that are used in the webs of the site collection. So the hidden taxonomy list have only keywords and taxonomies used in the webs of the site collection. You can get the contents of the hidden list by browsing “http://mysitecollection/Lists/TaxonomyHiddenList” where mysitecollection is your site collection url. Few things to notice for the hidden list:

  • In case of edit mode, the taxonomy is read from central administration and as user save the list item, the taxonomy/keywords is saved from hidden list.
  • In case of viewing an item, it’s for sure that the taxonomy/keyword is in hidden list (it was put in hidden list during edit mode). So the taxonomy/keyword is shown from hidden list.

 

Event Receiver for Taxonomy Hidden List

Now we know the taxonomies are kept in a hidden list at site collection level. And in every hour,  the hidden list is synchronized with central administration site. So if we add event receiver for the hidden taxonomy list,then in every hour our event receiver will be fired while the hidden list will be synchronized. So adding an event receiver for hidden taxonomy list will solve the problem apparently. But there’s still problem. Not all taxonomies are added to the hidden list from central administration. Only the taxonomies added to subsites of site collection, are added to the hidden list. Now the problem of event receiver for taxonomies can be divided into two categories:

  • Need event receiver only for taxonomies used in subsite of site collection: If you need the event receiver only for taxonomies used in subsites of site collection, then having the event receiver for hidden taxonomies will do. However, you’ll have to wait for one hour to fire the event receiver.
  • Need event receiver for all taxonomies: If you need your event receiver to be fired for every taxonomy (added/edited/deleted) manipulation, then tapping the hidden taxonomy list will not work as not all taxonomies will be added from central admin to hidden taxonomy list. So what might be the solution for this? One solution is to create a custom timer job of your own and in that timer job, use all taxonomies in a test list. Since all taxonomies are used in your site, so all taxonomies will be synchronized with the site collection and your event receiver will be fired for all taxonomies.
 

Solution (Re-explained)

so if only care for taxonomies used in the site collection then adding an event receiver for hidden list will work. In every hour the hidden taxonomy lists will be synchronized and your event receiver for that hidden list will be fired.

However, if you need to care all taxonomies (not just taxonomies used in the site collection), then you need to follow the steps:

  1. Create a test list with one metadata field.
  2. Create a timer job to add all metadata from central admin to the list’s metadata field. The purpose of this job is make sure all taxonomies from central admin are used in site collection.
  3. Now add the event receiver for hidden list and you are done.

Saturday, December 25, 2010

SharePoint 2010: Linking SharePoint User to Active Directory User

While we are using SharePoint Foundation, Sometimes we need to get the active directory user details based on current logged in user. If you are using SharePoint Server then this is not a big deal as you can get the user details through user profile. However, if you are using SharePoint Foundation then there’s no shortcut way to getting user details. However, one of my client is using SharePoint Foundation and wanted to get the user details from active directory (say user’s First Name). Here’s how I’ve achieved this:

 

Step 1: Created a timer job to import user details from Active Directory

I have created a custom list to store imported data from Active Directory into SharePoint. The list looks like below

image

Figure 1: SharePoint List to keep Active Directory User Details

 

As shown in the figure 1, the SID is the key to map a SharePoint user to Active Directory User. The following code snippet shows the code to import data

First I created DTO class to represent LDAP User:

public class LdapUser
{
    public int ID { get; set; }
    public bool IsActive { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string SId { get; set; }
    public string DisplayName { get; set; }
    public string UserName { get; set; }
}

Figure 2: An DTO to keep Active directory and/or SharePoint list data

 

The following code is to get the Active Directory data into LdapUser DTO:

public IList<LdapUser> GetUsersFromActiveDirectory(string connectionString, string userName, string password)
{
    var users = new List<LdapUser>();
const int UF_ACCOUNTDISABLE = 0x0002; using (var directoryEntry = new DirectoryEntry(connectionString, userName, password, AuthenticationTypes.None)) { var directorySearcher = new DirectorySearcher(directoryEntry); directorySearcher.Filter = "(&(objectClass=user)(objectClass=person)(objectClass=organizationalPerson)(!objectClass=computer))"; var propertiesToLoad = new[] { "SAMAccountName", "displayName", "givenName", "sn", "mail", "userAccountControl", "objectSid" }; directorySearcher.PropertiesToLoad.AddRange(propertiesToLoad); foreach (SearchResult searchEntry in directorySearcher.FindAll()) { var userEntry = searchEntry.GetDirectoryEntry(); var ldapUser = new LdapUser(); ldapUser.DisplayName = NullHandler.GetString(userEntry.Properties["displayName"].Value); if (string.IsNullOrEmpty(ldapUser.DisplayName)) continue; ldapUser.Email = NullHandler.GetString(userEntry.Properties["mail"].Value); ldapUser.FirstName = NullHandler.GetString(userEntry.Properties["givenName"].Value); ldapUser.LastName = NullHandler.GetString(userEntry.Properties["sn"].Value); ldapUser.UserName = NullHandler.GetString(userEntry.Properties["SAMAccountName"].Value); var userAccountControl = (int)userEntry.Properties["userAccountControl"].Value; ldapUser.IsActive = (userAccountControl & UF_ACCOUNTDISABLE) != UF_ACCOUNTDISABLE; var sid = new SecurityIdentifier((byte[])userEntry.Properties["objectSid"][0], 0).Value; ldapUser.SId = sid; users.Add(ldapUser); } } return users; }

Figure 2: A method to get Active Directory User data in DTO format

 

I’ve used a helper class NullHandler above, which is shown below:

public class NullHandler
{
    public static string GetString(object value)
    {
        return (value == null || value == DBNull.Value) ? string.Empty : value.ToString();
    }
}

 

The above method GetUsersFromActiveDirectory return the Ldap dto from Active Directory. Then you need to save the Ldap dto into SharePoint. The following code shown the method that will save the Ldap dto in SharePoint list:

public void SaveActiveDirectoryUsersToSharePointList(SPWeb currentWeb, IList<LdapUser> ldapUsers)
{
    const string query = @"<Where><Eq><FieldRef Name='SID'/><Value Type='Text'>{0}</Value></Eq></Where>";
    var ldapUserList = currentWeb.Lists["LDAPUsers"];
    foreach (var ldapUser in ldapUsers)
    {
        SPQuery spQuery = new SPQuery();
        spQuery.Query =  string.Format(query, ldapUser.SId);
        var items = ldapUserList.GetItems(spQuery);

        SPListItem listItem;

        //if the user exists with the same Sid then update 
        //either create a new list item.
        if (items.Count == 1)
        {
            listItem = items[0];
        }
        else
        {
            listItem = ldapUserList.AddItem();
        }
        listItem[Constants.Lists.LdapUsersList.Email] = ldapUser.Email;
        listItem[Constants.Lists.LdapUsersList.FirstName] = ldapUser.FirstName;
        listItem[Constants.Lists.LdapUsersList.LastName] = ldapUser.LastName;
        listItem[Constants.Lists.LdapUsersList.DisplayName] = ldapUser.DisplayName;
        listItem[Constants.Lists.LdapUsersList.IsActive] = ldapUser.IsActive;
        listItem[Constants.Lists.LdapUsersList.PID] = ldapUser.SId;
        listItem[Constants.Lists.LdapUsersList.UserName] = ldapUser.UserName;
        listItem.Update();
    }
}

Figure 3: A method to save DTO (LdapUser) in SharePoint list.

 

In the above method SaveActiveDirectoryUsersToSharePointList, if a listitem with the same SId as in the LdapUsers list, then the list item is updated or a new one is added. So SId is key to synchronize list item and Active Directory item.

 

After User data is imported from Active Directory to SharePoint, the SharePoint list has use details. As shown in the image below, the user doceditor properties in Active Directory is shown on the left side whereas the imported SharePoint list in right side.

image

Figure 4: Active Directory User and SharePoint list item side-by-side

 

Finally you can create a timer job to sync data from Active Directory to SharePoint list. However, I’m skipping this step for brevity.

Step 2: Retrieve SPUser’s details from SharePoint List where Active Directory data imported

As shown in the code below, you can get current SharePoint user’s SId by accessing SPUser’s Sid property. Once you have the sid you can query the list (LDAPUsers) where you imported the user data from Active Directory.

var ldapList = currentWeb.Lists["LDAPUsers"];
var currentUserSid = currentWeb.CurrentUser.Sid;
var query = new SPQuery();
query.Query = string.Format(@"<Where>
                    <Eq>
                        <FieldRef Name='PID'  />
                        <Value Type='Text'>{0}</Value>
                    </Eq>
                </Where>", sid);
var items = ldapList.GetItems(query);
if (items.Count == 1)
{
    var ldapUserListItem = items[0];
}

Conclusion

So the Active directory sid is mapped to current SPUser’s Sid. So you can access the Active Directory user’s Sid using code shown below:

var sid = new SecurityIdentifier((byte[])userEntry.Properties["objectSid"][0], 0).Value;

Then you can get the SharePoint User’s Sid by using the code snippet below:

SPContext.Current.Web.CurrentUser.Sid

Once you have the mapping, you can import any data from Active Directory to SharePoint list, sync the data with timer job and get the data of current logged in user from SharePoint list.

Tuesday, December 14, 2010

SharePoint 2010: Create Taxonomy Error “Term set update failed because of save conflict.”

I was trying to answer a user’s problem in MSDN forum. He was trying to add a term in term store. Using the term adding code (that I have taken from the post) I was getting the error “Term set update failed because of save conflict.” while I was calling the CommitAll method.

 

Problematic Code

My code is as shown below:

public static void AddTerminTermStoreManagement(string siteUrl, string termSetName, string Term)
{
    try
    {
        using (var siteTerm = new SPSite(siteUrl))
        {
            var sessionTerm = new TaxonomySession(siteTerm);
            var termStoreTerm = sessionTerm.DefaultSiteCollectionTermStore;
                    
            var collection = termStoreTerm.GetTermSets(termSetName, 1033);
            var termSet = collection.FirstOrDefault();

            if (!termSet.IsOpenForTermCreation)
            {
                termSet.IsOpenForTermCreation = true;
            }

            termSet.CreateTerm(Term, sessionTerm.TermStores[0].DefaultLanguage);
            termStoreTerm.CommitAll();
        }

    }
    catch
    {

    }

}

After investigating the code I had found the problem was in the line as shown in the above code snippet with yellow marker (termSet.IsOpen..).

 

Solution

I have fixed the error by modifying the code as shown below. I had just called the CommitAll just after changing the value of IsOpenForTermCreation as shown below:

public static void AddTerminTermStoreManagement(string siteUrl, string termSetName, string Term)
{
    try
    {
        using (var siteTerm = new SPSite(siteUrl))
        {
            var sessionTerm = new TaxonomySession(siteTerm);
            var termStoreTerm = sessionTerm.DefaultSiteCollectionTermStore;
                    
            var collection = termStoreTerm.GetTermSets(termSetName, 1033);
            var termSet = collection.FirstOrDefault();

            if (!termSet.IsOpenForTermCreation)
            {
                termSet.IsOpenForTermCreation = true;
                termStoreTerm.CommitAll();
            }

            termSet.CreateTerm(Term, sessionTerm.TermStores[0].DefaultLanguage);
            termStoreTerm.CommitAll();
        }

    }
    catch
    {

    }
}

 

Conclusion

So the problem was that when I changed the value of IsOpenForTermCreation, there was a commit pending. So without committing, I created a new term and tried to committed. So the save conflict error was thrown. If you get the same error in different scenario then u can check if u have any pending commit that u have not committed.

Monday, December 13, 2010

SharePoint 2010 Error: Accessing lookup field values using Elevated web generates exception “Value does not fall within the expected range” when the lookup fields exceeds the lookup threshould

I have a list which has 9 look columns. By default SharePoint doesn’t allow that more than 8 lookup columns (However you can modify the value). When I tried to access the list from SharePoint list view, I could access the list without any problem. I was even accessing the list from webpart code and it was working fine.

 

All of sudden I had found some piece of my code is not working. While I was trying to access the 9th lookup column value of the SharePoint List from webpart  code I had got the error “Value does not fall within the expected range”. After investigating the problem I had found the 9th field doesn’t exist in the list. After spending some time on the issue, the final summary is:

So, If your list’s lookup columns exceeds the Lookup column threshold and if you try to access the list lookup field value from code using elevated web, then you will get the error “Value does not fall within the expected range”. And interestingly the exception will be thrown for not all lookup fields rather the ‘n+1’ lookup fields whereas the n is the lookup field threshold value."

Problem At a glance

So here is how you can reproduce the issue:

  1. You have set the list view lookup threshold to N.
  2. Then you have a list MyList with more than N lookup fields.
  3. If you try to access the list from SharePoint UI, you can access the list.
  4. Even if you try to access the list from code with SharePoint object model using SPContext.Current.Web you can access all lookup field values.
  5. However, If you try to access the lookup field values using Elevated web (code under SPSecurity.RunWithElevated) you will get error for N+1 lookup fields.

Sample Code to regenerate the issue

The following code block I used to test the issue. To run the test I had set the lookup column limit to two from central admin. So the code can read first two field’s lookup value but get exception to read the third.

var webId = SPContext.Current.Web.ID;
var siteId = SPContext.Current.Site.ID;
SPSecurity.RunWithElevatedPrivileges(
() =>
{
    using (SPSite site = new SPSite(siteId))
    {
        using (SPWeb web = site.OpenWeb(webId))
        {
            var fields = new string[] {"LooupField1", "LookupField2", "LookupField3"};
            var lookupTestList = web.Lists["LookupTestList"];
            foreach (SPListItem spListItem in lookupTestList.Items)
            {
                foreach (var f in fields)
                {
                    try
                    {
                        //get excception here for reading lookup col 3
                        var value1 = spListItem[f];
                    }
                    catch (Exception exception)
                    {
                        Response.Write(string.Format("Error in reading field: {0}. Error: {1}", f, exception.Message));
                    }
                }
            }
        }
    }
});

The points to notice to regenerate the issue:

  • The exception is not thrown if I try the same code shown above in console application. However, the exception is thrown when I tried to run it webpart.
  • The exception is shown when I try to get the items by accessing List.Items. However the exception is not thrown when I get the item using any other means (like list.GetItemById etc)

Thursday, December 2, 2010

SharePoint 2007 and Visual Studio 2010: Resolution of “w3wp process does not attach” problem

Recently we have moved to Visual Studio 2010 for our SharePoint project. In Visual Studio 2008 we were using WSP builder to manage SharePoint deployment. After moving to Visual Studio 2010, we have used beta version of WSP 2010. We had converted our SharePoint projects successfully to VS 2010, we tested deployments and others are working great with VS 2010 and WSP 2010. However when we tried to start developing, we had found that w3wp process can’t be attached. We tried different approach, sometimes the attaching worked but only for the first time. If we detach the debugger and then try to attach again the debugger doesn’t work for the second time.

 

After Googling I had found another person Patrick Lamber has solved the problem in his blog. I’m reposting it in details hoping this might be helpful for someone.

  1. Click Debug => Attach to Process. The “Attach to Process” dialog comes up.
  2. In the “Attach to Process” dialog, click Select button, which will show “Select Code Type” dialog as shown below: image Figure 1: Change Code Type dialog

  3. In the “Select Code Type” dialog, select “Debug these types of code” and then check option for “Managed (v2.0..)
  4. You are done. Now you can try attaching debugger. Hope it’ll work. Smile