Upload a file to Sharepoint 2007 using webservices with a specific content type

July 3, 2008


I’m moving to a new site ! Please visit this article to by clicking the following url: Upload a file to Sharepoint 2007 using webservices with a specific content type

These days I’ve been involved in a migration project from Sharepoint 2001 to MOSS 2007, what  I had to do was move all the documents from a document workspace in SPS2001 to a WSS3.0 document library. Since some of the custom metada had to be migrated with the documents and the SP2001 profiles had to be converted to MOSS content types I decided that the best approach would be to use MOSS 2007 web services interface, most specifically the Copy.asmx web service which has a CopyIntoItems method that enables to upload files to a document library.

Since SP2001 is an obsolete platform, I won’t talk here about how to access it and deal with the PKMCDO COM library (if there is enough interest, I might write an article about that) and I will focus on the file upload web service and the more challenging topic (imho) which is how to set a content type to the uploaded documents.

The first thing to do would be to get the files uploaded to MOSS. As I told, I will be using the Copy.asmx web service. Following is the code of the upload function:

        public void UploadFile(string destinationFolderPath,
                               byte[] fileBytes,
                               string fileName,
                               bool overwrite,
                               string sourceFileUrl,
                               string lastVersionUrl)
        {

            List<Sharepoint.FieldInformation> fields = new List<Sharepoint.FieldInformation>();
            Sharepoint.FieldInformation fieldInfo;

            fieldInfo = new Sharepoint.FieldInformation();
            fieldInfo.Id = Microsoft.SharePoint.SPBuiltInFieldId.Title;
            fieldInfo.Value = "New title";
            fieldInfo.DisplayName = "Title";
            fieldInfo.Type = YetAnotherMigrationTool.Library.SP2007.Sharepoint.FieldType.Text;
            fieldInfo.InternalName = "Title";
            fields.Add(fieldInfo);

            string[] url;
            if (string.IsNullOrEmpty(destinationFolderPath))
                url = new string[] { string.Format("{0}/{1}/{2}", _siteUrl, _name, fileName) };
            else
                url = new string[] { string.Format("{0}/{1}/{2}{3}", _siteUrl, _name, destinationFolderPath, fileName) };
            Sharepoint.CopyResult[] result;

            Sharepoint.Copy service = new Sharepoint.Copy();
            service.Url = _siteUrl + "/_vti_bin/Copy.asmx";
            service.Credentials = new NetworkCredential(Settings.Instance.User, Settings.Instance.Password);
            service.Timeout = 600000;

            uint documentId = service.CopyIntoItems(sourceFileUrl, url, fields.ToArray(), fileBytes, out result);
        }

There is nothing really difficult there. It necessary to call the webservice with an account that has Manage List permissions on the target document library. Also we should estimate the maximum document size we plan to upload and network speed and set the maxRequestLength and executionTimeout values of the httpRuntime section in our web.config in accord to avoit any possible Timeout Exception.

I’ve included an example of how would we set any of the document’s properties when calling the CopyIntoItems method. I’ve used the Title property to show that even read only fields can be assigned with that method. So, can all the fields be assigned ? Unfortunately not.

One of my requirements was to map SP2001 document profiles to MOSS content types, in order to do that, I created the content types at the server with their custom columns and looked for a way to pass this info as a parameter to the CopyIntoItems service call. There was not a parameter such like that, but I was confident that I would be able to set the ContentType field just as I did with the Title field and that would be all. However, that solution didn’t work. No matter if you set the ContentType or the ContentTypeId fields before calling the Copy.asmx service that the uploaded document content type will always be the default content type of the library.

After doing that I tried another approach. As I saw that the default content type of the library was assigned to the uploaded files, I tried to change the default content type of the list before uploading each file. Unfortunately, I couldn’t find any webservice that provided that functionality, I tried the UpdateList method of the Lists.asmx service with a custom xml scheme where the <DEFAULT></DEFAULT> section of the content type element was replaced by the one I wanted to be with no luck. I couldn’t manage to get the default content type of a document library changed !

Finally, I tried the last solution I had thought of. It was to use the UpdateListItems method of the Lists.asmx service to change the item ContentType field. The reason why I didn’t try this approach first instead of trying to change the default content type of the document library (which would seem the obvious thing to do) was because I didn’t expect it to work. If I hadn’t been able to set that readonly field with the CopyIntoItems method of the Copy.asmx service it would be expected for the UpdateListItems to have the same behaviour. But it doesn’t. It is possible to update any field with this method and eventually change a document content type.

So here is the method I use to call that service and change the content type:


        public void SetContentType(List<string> ids, string contentType)
        {
            ListsService.Lists service = new YetAnotherMigrationTool.Library.SP2007.ListsService.Lists();
            service.Url = _siteUrl + "/_vti_bin/Lists.asmx";
            service.Credentials = new NetworkCredential(Settings.Instance.User, Settings.Instance.Password);

            string strBatch = "";
            for (int i = 1; i <= ids.Count; i++)
            {
                strBatch += @"<Method ID='"+i.ToString()+@"' Cmd='Update'><Field Name='ID'>" + ids[i-1] + "</Field><Field Name='ContentType'>"+contentType+"</Field></Method>";
            }
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement elBatch = xmlDoc.CreateElement("Batch");
            elBatch.SetAttribute("OnError", "Continue");
            elBatch.SetAttribute("ListVersion", "10");
            elBatch.SetAttribute("ViewName", "");
            elBatch.InnerXml = strBatch;

            result = service.UpdateListItems(_name, elBatch);
        }

I called this method passing as parameters the collection of ids I got from the CopyIntoItems call, grouping by content type and it worked ! Now I can set the content type to my uploaded files and my first big issue with that migration project is solved. Currently I am working on a more complicated step which is how to migrate the version history from the SP2001 documents to the MOSS 2007, if I finally manage to solve it I’ll surely post a new article on that topic. That’s all for now, as always, comments are welcome !

Doctora Rosa Grau, Pediatra, Sant Quirze del Valles
Advertisements

SPWebConfigModification: configure applicationSettings with a Feature

March 5, 2008

Features are one of the coolest additions in WSS 3.0 if not the most. Not only they help us as software developers to encapsulate our code in redistributable packages but also simplify mainteinance tasks and deployment to server administrators. Ultimately, we’d like to be able to deploy our site definition with just a walk-through installer that would activate the required features.

Quite often I have to make some changes in the web.config files of the web applications where my solutions are to be deployed. Manifest.xml files of Sharepoint Solutions helps us with this task providing us with some easy ways to alter the web.config files, unfortunately, we are limited at registering safe controls and alter security policies which sometimes is not enough.

What I usually want to do, is to deploy a particular applicationSettings section for an assembly used in my solution, in this example, I have an assembly that uses an httpHandler to retrieve some Xml and I have configured the url of the handler in a settings file, this way, I’ll be able to change it if needed. This is the settings sections I need to include in the web.config file of the web application in order to do that:

<configuration>
  <configSections>
    <sectionGroup name=”applicationSettings” type=”System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″> <section name=”MyAssembly.Properties.Settings” type=”System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″ requirePermission=”false” /></sectionGroup>
  </configSections>
  <applicationSettings>
    <MyAssembly.Properties.Settings>
      <setting name=”HttpHandlerUrl” serializeAs=”String”>
        <value>https://oricode.wordpress.com</value&gt;
      </setting>
    </MyAssembly.Properties.Settings>
  </applicationSettings>
</configuration>

There is no easy way to automate this process. The usual procedure would be to deploy the solution and then ask the administrators to change the web.config files of all the involved web applications manually but this is what I was trying to avoid. I decided to take a look at the new SPWebConfigModification objects in the SPWebApplication object and use them in a Feature Receiver that would modify the web.config file.

At first sight, it seems that this objects are specially suited for this task and that it would be an easy thing to do, but it is not. SPWebConfigModification class is usefull when adding and replacing sections in the configuration files but it’s not easy to modify existing ones by adding new child sections. Basically, what you do is to select a section by providing a xpath and provide the new xml value for the section, but what happens when we don’t want to replace the section but just append new child nodes to it ?

For example in my case, I had to consider the following scenarios:

1. The webconfig file didn’t had the applicationSettings section defined (which is something very usual)
2. The webconfig file already had the applicationSettings section defined (by another assembly) and I had to append the new section for my assembly in it

So I couldn’t just replace the whole section with the xml for my assembly because this might have an impact in any already configured assembly used in the web application that relied in the applicationSettings section.

So here is the code of my FeatureActivated event where I would handle those modifications:

Initialization:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    SPSite site = (SPSite)properties.Feature.Parent;
    SPWebApplication webApp = site.WebApplication;

Since there is no way to know if the applicationSettings section is already defined in the web.config file, my solution is to execute the modifications as if the section was defined and if that rises an exception, it would mean that it is not and then I could add it. I know it is not an elegant approach and I’d really appreciate it if anybody could come up with a better solution !

try
{
    webApp.WebConfigModifications.Clear();

    SPWebConfigModification configMod = new SPWebConfigModification();
    configMod.Name = @"section[@name='MyAssembly.Properties.Settings']";
    configMod.Path = "configuration/configSections/sectionGroup[@name='applicationSettings']";
    configMod.Sequence = 0;
    configMod.Owner = properties.Feature.DefinitionId.ToString();
    configMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
    configMod.Value = "";
    webApp.WebConfigModifications.Add(configMod);

    configMod = new SPWebConfigModification();
    configMod.Name = @"MyAssembly.Properties.Settings";
    configMod.Path = "configuration/applicationSettings";
    configMod.Sequence = 0;
    configMod.Owner = properties.Feature.DefinitionId.ToString();
    configMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
    configMod.Value = "" +
                            "" +
                                "https://oricode.wordpress.com" +
                            "" +
                       "";
    webApp.WebConfigModifications.Add(configMod);

    webApp.Farm.Services.GetValue().WebConfigModifications.Clear();
    webApp.Update();
    webApp.Farm.Services.GetValue().ApplyWebConfigModifications();
}

If the section was already defined, the previous code would launch a <em>SPException</em>. Unfortunately, since the exception is not better typed, there is no way to ensure that the error is caused by the applicationSettings section not being configured (we could always read the exception message that will tell us if so). So if the exception is raised, we have to add the sections as new <em>SPWebConfigModification</em> objects, the previous modifications were added to the <em>WebConfigModifications</em> collection of the <em>SPWebApplication</em> object so there’s no need to add them again.

catch (SPException ex)
{
    SPWebConfigModification configMod = new SPWebConfigModification();
    configMod.Name = @"sectionGroup[@name='applicationSettings']";
    configMod.Path = "configuration/configSections";
    configMod.Sequence = 0;
    configMod.Owner = Guid.NewGuid().ToString();
    configMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
    configMod.Value = " " +
                      "";
    webApp.WebConfigModifications.Insert(0, configMod);

    configMod = new SPWebConfigModification();
    configMod.Name = @"applicationSettings";
    configMod.Path = "configuration";
    configMod.Sequence = 0;
    configMod.Owner = Guid.NewGuid().ToString();
    configMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
    configMod.Value = "";
    webApp.WebConfigModifications.Insert(0, configMod);

    webApp.Update();
    webApp.Farm.Services.GetValue().ApplyWebConfigModifications();
}

Note that this two last configuration sections have their Owner property set to a random Guid string. This is because the owner property is used in the feature deactivating event for removing the web.config changes, however, I can’t remove the applicationSettings sections because after my feature is activated, some other application settings might be registered in those sections by the server administrator and If then my feature was deactivated, all of them would be deactivated as well.

There are also a lot of issues I’ve faced when using SPWebConfigModification objects, like modifications that are persisted in the WebConfigModifications collections of the webApplication object before any call to the update() method is made (this is why I’ve set the Clear() methods of the collection at the start of the code) and other similar issues. Feel free to comment any strange behaviour you might find and I’ll try to help you If possible.

And that’s it, activating this feature will modify the web.config file of the web application as desired. However, I hope in future releases of Sharepoint object model we will see a better way to achieve this.

To end, I’d like to point that this solution should be tested in a farm deployment where multiple servers are involved, use with caution in that scenarios !

That’s all for now 🙂 as always, comments are welcome !


Sharepoint: publish a webservice with your solution

February 29, 2008

In this article I’ll cover how can you publish a WebService with your solution that will be accessible at every site in all the web applications where the solution is deployed. To keep thing simple, I’ve chosen a simple Helloworld webservice but with this approach we could publish any webservice or .aspx pages or a httpHandler

What we will do is to publish the service in the _vti_bin sharepoint folder of our site. To do this, we must first copy the .asmx file of the service in the Common FilesMicrosoft Sharedweb server extensions12ISAPI folder of our server.

<%@ WebService Language=”c#” Class=”MyAssembly.Services.HelloWorldService,MyAssembly.Services”%>

This was the .asmx file we want to distribute and following is the .DDF file from which we’ll generate the solution, I’d like to show that you could also publish a web.config file for the _vti_bin/MyAssembly folder, we would want to do so if, for example, were deploying an httpHandler instead of a webservice or if we need to configure some AppSettings parameters.

; Solution.DDF
FeaturesISAPIMyAssemblyweb.config ISAPIMyAssemblyweb.config
FeaturesISAPIMyAssemblyHerramientas.asmx ISAPIMyAssemblyHelloWorldService.asmx
ServicesbinDebugMyAssembly.Services.dll MyAssembly.Services.dll

Then we must define the manifest.xml file used in the solution deployment:

<RootFiles>
<RootFile Location=ISAPIMyAssemblyweb.config />
< RootFile Location=ISAPIMyAssemblyHelloWorldService.asmx />
</RootFiles>

<Assemblies><Assembly DeploymentTarget=WebApplication Location=MyAssembly.Services.dll /></Assemblies>

Remember to set the appropiate assembly permission at the CodeAccessSecurity section of the manifest.xml file if not running under Full Trust mode or not deploying the assembly in the GAC.

Finally, here is the easy code of our Helloworld service:

namespace MyAssembly.Services
{
    [WebService(Namespace = "http://MyAssembly.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class HelloWorldService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Greetings from this Service !";
        }

    }
}

Now, after deploying the solution we can access the webservice adding _vti_bin/MyAssembly/HelloWorldService.asmx at our site url (for those sites we have deployed the solution to !). However, If we have deployed the solution in the GAC, the service should be able to all the sites in the server.

That’s all for now ! As always, feel free to comment anything you might find interesting !


Ajax webpart: Register scripts on UpdatePanel postbacks

February 25, 2008

These days I’m developing custom ajax enabled webparts for a sharepoint solution. I’ve created a base webpart class named AjaxEnabledWebpart from which all the webparts inherit. This webpart has an updatepanel control where all the child webparts are supposed to create the controls they need so finally, everycontrol is rendered inside the updatepanel and the webpart has the expected asp.net ajax behaviour of partial rendering the pages. Some of the webparts have to render content created dinamically by javascript code as you will see later in the code, in this case, content is rendered this way because we are supposed to give an accessible front end in our webpart (this is supporting non-script enabled browsers) so we are using the typical <noscript><script> tag approach. However, the problem we faced is that the content rendering script code (or any script code at all) was not executed during Ajax postbacks of the webpart. The solution we found to this problem was to register the scripts with the Sys.WebForms.PageRequestManager.getInstance().add_endRequest() function. Following is the code we used. First, for reference only, I’ll post the main code of the AjaxEnabledWebpart which somebody might find useful

protected override void CreateChildControls()
{
    base.CreateChildControls();

    EnsurePanelFix();

    _updatePanel = new UpdatePanel();

    _scriptHandler = ScriptManager.GetCurrent(this.Page);
    if (_scriptHandler == null)
    {
        //no script manager registered
        _scriptHandler = new ScriptManager();
        _scriptHandler.ID = "scriptManager";
        this.Controls.Add(_scriptHandler);
    }

    _updatePanel.ID = "updatePanel_";
    _updatePanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
    _updatePanel.ChildrenAsTriggers = true;

    CreatePanelControls(_updatePanel.ContentTemplateContainer.Controls);

    this.Controls.Add(_updatePanel);
}

protected virtual void CreatePanelControls(ControlCollection PanelControls)
{
}

The only relevant code from the AjaxEnabledWebpart is the CreateChildControls method, which creates the UpdatePanel control and calls the virtual method CreatePanelControls passing as a parameter the control collection where child pages are supposed to add the controls. Also, you can find the EnsurePanelFix implementation on my previous post at this linkAfter that, here is the code of a custom HtmlTable used in some webparts which contents are rendered using javascript code (if scripting is enabled in the client browser). The posted code is a function which returns the html code to be inserted in a cell’s InnerHtml property of a table:

private string _getSelectAllHtml(HtmlTableCell cell)
{
    string ret = "";

    ret += "" +
                "select all" +
             "" +
             "" +
                "// " +
            "" +
           "";

    string fn = "" +
                    "//<!&#91;CDATA&#91; n" +
                        "function _loadSelectAllHeader() { " +
                            "cab = '<a href="#" title="CellTitle">" +
                            "<img src="_layouts/img/selectall.gif" alt="select all">';" +
                            "var capa = document.getElementById("" + cell.Attributes&#91;"Id"&#93; + "");" +
                            "capa.innerHTML = cab;n" +
                        " }" +
                    "//&#93;&#93;> " +
                "";

    Page.ClientScript.RegisterClientScriptBlock(typeof(ResultsTableMod47),
                                "_loadSelectAllHeader",
                                fn);

    Page.ClientScript.RegisterStartupScript(typeof(ResultsTableMod47),
                                "initRequestScriptHandler_loadSelectAllHeader",
                                "Sys.WebForms.PageRequestManager.getInstance().add_endRequest(_loadSelectAllHeader);");

    return ret;
}

The relevant lines of the code are the calls to RegisterClientScriptBlock and RegisterStartupScript methods. The first one register our content-rendering script while the second one adds this function to the end_request handler of the PageRequestManager instance. This will ensure that the script is called after each Ajax postback of the pages.Further scripts can be registered in the end_request handler by calling the add_endRequest method for each script we want to be executed after a postback. However, we have to considered that this script will be executed for all the postbacks and not only for the ones generated by this webpart’s updatepanel control, so if more than one updatepanel is present on the page, we might have to include some checking at the startup of our code to ensure that our script must be executed. In this case this was not necessary since the script could be executed again without any problem.

That’s all for now ! I hope it’s useful…. coments are welcome 🙂


Ajax enabled webpart not working on second postback

February 20, 2008

Trying to develope an Ajax webpart with aspx.net 2.0 I came up with this msdn example  which seemed to be exactly what I was looking for. I followed the steps suggested by the article but I ended up with some weird behaviour from the webpart when finally deployed in my Sharepoint server.

Apparently everything was working as expected and when I clicked the webpart the first time, the ajax method was correctly executed and the “Hello! XXX” text succesfully displayed. However, clicking again the button didn’t seem to do anything, also, other page actions that should submit the form and do some action (like the “Edit Page” item from the “Site Actions” menu) stopped working.

I finally found that the problem was in the EnsurePanelFix function of the example which was not working as expected

private void EnsurePanelFix()
{
   if (this.Page.Form != null)
   {
     String fixupScript = @"
     _spBodyOnLoadFunctionNames.push(""_initFormActionAjax"");
     function _initFormActionAjax()
     {
       if (_spEscapedFormAction == document.forms[0].action)
       {
         document.forms[0]._initialAction =
         document.forms[0].action;
       }
     }
     var RestoreToOriginalFormActionCore =
       RestoreToOriginalFormAction;
     RestoreToOriginalFormAction = function()
     {
       if (_spOriginalFormAction != null)
       {
         RestoreToOriginalFormActionCore();
         document.forms[0]._initialAction =
         document.forms[0].action;
       }
     }";
   ScriptManager.RegisterStartupScript(this,
     typeof(SayHelloWebPart), "UpdatePanelFixup",
     fixupScript, true);
   }
}

So what I did is to provide my own implementation of that function. What we needed is to invalidate the onSubmit wrapper that Sharepoint calls in order to ensure some callback scenarios, this can be done by setting to true the _spSuppressFormOnSubmitWrapper variable so I replaced the original function by the following implementation

private void EnsurePanelFix()
        {
              ScriptManager.RegisterStartupScript
                (this,
                 typeof(AjaxEnabledWebpart),
                 "UpdatePanelFixup",
                 "_spOriginalFormAction = document.forms[0].action; _spSuppressFormOnSubmitWrapper=true;",
                 true);
}

And that’s it ! my Ajax webpart is working properly no matter how many times we click the Hello World ! button 🙂


Exchange Server Event Sink in c#

February 15, 2008

I’ve been asked to make some minor modifications to a component I developed some time ago. Reviewing the code, I thought it would be a nice topic to talk about it.

This component is an Event Sink which is supposed to be installed in an Exchange Server that parses all incoming email messages (depending on the rules defined on it’s registration, but this is a topic I’ll leave for another post!) and check if they contain some tag in it’s subject field. If so, it does some processing and changes their subject by adding a #OK or #ERROR tag depending on the process outcome. This tag is meant to be used by the Outlook to determinate on which folder should the message be placed (defined by some Outlook rule by the client)

What we need in order to create an Exchange event sink is a class that implements an interface defined in th COM+ component cdoex.dll which we can find in the bin folder of our exchange server. In order to do that we must build the interop .net assembly that will wraps us this library in managed code. We can do that with the following call to tlbimp

sn –k sn.key 
tlbimp cdoex.dll /keyfile:sn.key /out:Interop.cdoex.dll /namespace:CDO

Then we add this reference to our assembly and creates a class that implements the ISMTPOnArrival interface. There are a lot of other interfaces in the cdoex library depending on when do we want our event sink to be executed, for this example I needed to be when a message arrived and this is why I choosed this particular interface. Here is the class declaration:

[Guid("021079E3-6FCB-491b-A78F-81BC31A1EC9D")]
[ComVisible(true)]
public class MyEventSink : ISMTPOnArrival, IEventIsCacheable

So following is the implementation of the ISMTPOnArrival interface:

void ISMTPOnArrival.OnArrival(IMessage Msg, ref CdoEventStatus EventStatus)
{
    try
    {
        //Do message processing
        //Here we can access all message's properies
        //for example the message body: Msg.TextBody

        //Change subject to OK
        Msg.Fields["urn:schemas:mailheader:subject"].Value = "#OK# " + Msg.Subject;

    }
    catch (Exception)
    {
        //Change subject to ERROR
        Msg.Fields["urn:schemas:mailheader:subject"].Value = "#ERROR# " + Msg.Subject;
    }
    finally
    {
        //Save message properties
        Msg.Fields.Update();
        Msg.DataSource.Save();
        EventStatus = CDO.CdoEventStatus.cdoRunNextSink;
    }
}

Then, all we need to do is register the .dll in the Exchange server but this is a topic I’ll cover in another post ! As always, feel free to leave any comment !


Active Directory Role Provider

February 14, 2008

Having an asp.net application with forms authentication enabled authenticate users against an Active Directory is an easy thing. All you have to do is use an ActiveDirectoryMembershipProvider and configure it’s connectionString property in the web.config file.

Retrieving user’s role information is a different thing. One would expect an ActiveDirectoryRoleProvider to connect to the Active Directory and retrieve the current user’s group information, however such object doesn’t exist.

What follows is a custom implementation of this role provider that queries an Active Directory and retrieves user’s group information.

First you have to define a class that inherits from System.Web.Security.RoleProvider in order to use it as your application role provider

public class CustomActiveDirectoryRoleProvider : System.Web.Security.RoleProvider

Next, you should retrieve the configuration information from the web.config file in the Initialize method

private string _loginProperty = "sAMAccountName";
private string _connectionString = string.Empty;
private string _applicationName = string.Empty;

public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
    _connectionString = config["connectionStringName"];
    _applicationName = config["applicationName"];
    if (!string.IsNullOrEmpty(config["attributeMapUsername"]))
        _loginProperty = config["attributeMapUsername"];
    base.Initialize(name, config);
}

At last, whe have to implement GetRolesForUser method where we will get all the groups where the current user belongs to (other methods such as IsUserInRole can be implemented later by querying the resultset from GetRolesForUser)

public override string[] GetRolesForUser(string userName)
{
    List allRoles = new List();

    DirectoryEntry root = new DirectoryEntry(WebConfigurationManager.ConnectionStrings[_connectionString].ConnectionString);
    foreach (DirectoryEntry entry in root.Children)
    {
        if (entry.SchemaClassName.ToLower() == "group")
        {
            object members = entry.Invoke("Members", null);
            foreach (object member in (IEnumerable)members)
            {
                DirectoryEntry child = new DirectoryEntry(member);

                if (_getProperty(child, _loginProperty) == userName)
                {
                    string name = _getProperty(entry, "name");
                    allRoles.Add(name != "" ? name : entry.Name);
                }
            }

        }
    }

    return allRoles.ToArray();
}

Finally I provide the _getProperty function source code

private string _getProperty(DirectoryEntry entry, string propertyName)
{
    if ((entry.Properties[propertyName] != null) &&
        (entry.Properties[propertyName].Value != null))
    {
        return entry.Properties[propertyName].Value.ToString();
    }

    return "";
}

And that’s it ! hope it’s useful and don’t forget to leave a comment if you think so 😉