Welcome to MSDN Blogs Sign in | Join | Help

Define custom new/edit/display forms for Content Types

Recently, I got a requirement to define custom pages for a content type item. What am I talking about? If that’s the question these are the pages which open when you try to open, edit and create an item from the list.

So the best way to customize these pages is to add these pages entry to the custom content type. You can create your page (Either same for all the three or separate pages) and add them to the layouts folder.

You can then reference these pages in the content type’s element.xml file. So your content type elements.xml file would look like:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <ContentType ID="0x010800b1684200801b11ddbcb20050c2490048"
   4:                Name="Publication Approval Content Type5"
   5:                Group="Publication Approval Workflow"
   6:                Description="5 Custom Workflow Task for the Publication Approval Workflow"
   7:                Version="0"
   8:                Hidden="FALSE">
   9:     <FieldRefs>
  10:       <FieldRef ID="{81561b40-7aae-11dd-9b5a-0050c2490048}"
  11:                 Name="_NotifyIR"/>
  12:     </FieldRefs>
  13:     <XmlDocuments>
  14:       <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
  15:         <FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
  16:           <Display>_layouts/PublicationApprovalForms/TaskEditForm/TaskEditForm.aspx</Display>
  17:           <Edit>_layouts/PublicationApprovalForms/TaskEditForm/TaskEditForm.aspx</Edit>
  18:           <New>_layouts/PublicationApprovalForms/TaskEditForm/TaskEditForm.aspx</New>
  19:         </FormUrls>
  20:       </XmlDocument>
  21:     </XmlDocuments>
  22:   </ContentType>
  23: </Elements>

Add this code to the elements.xml of the content type and then re-install and activate the content type. And Voila your pages would be customized.

One more thing, if you would like to do the above programmatically then it is easier then the above way. (Of course you would have to create the aspx pages.)

   1: using (SPSite site = new SPSite("http://Site/"))
   2: {
   3:     using (SPWeb web = site.OpenWeb())
   4:     {
   5:         SPList list = web.Lists[documentlibrary];
   6:         SPContentType ct = list.ContentTypes[contentType];
   7:         if (!on)
   8:         {
   9:             ct.EditFormUrl = string.Empty;
  10:             ct.NewFormUrl = string.Empty;
  11:             ct.DisplayFormUrl = string.Empty;
  12:          }
  13:          else
  14:          {
  15:              ct.EditFormUrl = "_layouts/whateverEdit.aspx";
  16:              ct.NewFormUrl = "_layouts/whateverNew.aspx";
  17:              ct.DisplayFormUrl = "_layouts/whateverDisplay.aspx";
  18:          }
  19:          ct.XmlDocuments.Delete("http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url");
  20:          ct.Update();
  21:          list.Update();
  22:      }
  23:  
  24: }

BDC User Permission List ---- Add users from API

Recently I came across an interesting request from my customer, who wanted to add users to the “BDC User Permission List” in the central admin. There is a How To article on MSDN and SDK – “How to: Add an Access Control Entry to a Metadata Object”, but unfortunately that doesn’t work.

 

After researching, I found that the API to use is the “ApplicationRegistry” API. This API provides access to all of the line-of-business (LOB) systems and LOB system instances registered in the Business Data Catalog. This is the top-level object in the Business Data Catalog's object model.

 

The sample in SDK tries to use one of the LOBInstances and then tries to add the users with their permissions to the BDC User Permission list. But this doesn’t work. To add the users to the BDC permission list, you need to add the users to the ApplicationRegistry object, which is at a level above the LOBInstances class. Below is the code I used to achieve the functionality:

<Code>

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Office.Server.ApplicationRegistry.Administration;

using Microsoft.Office.Server.ApplicationRegistry.Infrastructure;

using WSSAdmin = Microsoft.SharePoint.Administration;

using OSSAdmin = Microsoft.Office.Server.Administration;

 

namespace C

{

    class GetStartedAndCreateSystem

    {

        const string yourSSPName = "SharedServices1";

        const string userName = "domainname\\username";

 

        static void Main(string[] args)

        {

            SetupBDC();

            SetAccessControlListForSpecifiedUser();

            Console.WriteLine("Press any key to exit...");

            //Console.Read();

        }

        static void SetupBDC()

        {

            SqlSessionProvider.Instance().SetSharedResourceProviderToUse(yourSSPName);

        }

 

        public static void SetAccessControlListForSpecifiedUser()

        {

            //replace the domain and user names here

            String currentIdentity = userName;

            try

            {

                ApplicationRegistry registry = ApplicationRegistry.Instance;

                IAccessControlList acl = registry.GetAccessControlList();

                acl.Add(new IndividualAccessControlEntry(currentIdentity, BdcRights.SetPermissions | BdcRights.Execute));

                registry.SetAccessControlList(acl);

            }

            catch (Exception Ex)

            {

                //your exception handling code here

            }

            Console.WriteLine("Done");

        }

    }

}

</Code>

Virtual Earth in SharePoint

I recently was working on a very cool requirement where customer wanted to implement virtual earth pins view in a SharePoint web part.

On working for a long time using javascripts and XSL I was finally able to crack the solution.

So, here are some instructions that will hopefully get you up and running quickly.

Assuming that you have a list with Title and Description fields that includes 2 additional fields called "Lat" and "Long", create a web part page in SharePoint Designer and add a Data View of the list containing the locations.

Add the following script and html to the page above the data view web part:

   1: <script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5"> </script> 
   2: <script type="text/javascript"> 
   3: var map = null; 
   4: // Loads the Virtual Earth map control 
   5: function GetMap() 
   6: { 
   7: map = new VEMap('myMap'); 
   8: map.LoadMap(new VELatLong(0,0), 1,'r' ,false); 
   9: AddPins(); 
  10: } 
  11: // Places a pushpin on the map using the parameters given, iconurl is ignored 
  12: function AddPin(lat, lon, iconurl, title, desc) 
  13: { 
  14: var shape = 
  15: new VEShape(VEShapeType.Pushpin, 
  16: new VELatLong(lat,lon)); 
  17: shape.SetTitle(title); 
  18: shape.SetDescription(desc); 
  19: map.AddShape(shape); 
  20: } 
  21: // Programmatically adds func as a handler for the onload event 
  22: // This method has been used by many developers, but the code is 
  23: // via the ViaVirtualEarth Wiki 
  24: // http://www.viavirtualearth.com/Wiki/Load+VE+control+without+body+onload.ashx. 
  25: function addLoadEvent(func) 
  26: { 
  27: var oldonload = window.onload; 
  28: if (typeof window.onload != 'function') 
  29: { window.onload = func; } 
  30: else 
  31: { window.onload = function() 
  32: { oldonload(); func(); } 
  33: } 
  34: } 
  35: addLoadEvent(GetMap); 
  36: </script> 
  37: <div id='myMap' style="width:800px; height:600px;"></div> 

 

Add the following XSL template section to the Data View web part

   1: <xsl:template name="AddMapPins"> 
   2: <xsl:param name="Rows"/> 
   3: <xsl:text disable-output-escaping="yes"><![CDATA[ 
   4: <script type="text/javascript"> 
   5: function AddPins() 
   6: { 
   7: ]]></xsl:text> 
   8: <xsl:for-each select="$Rows"> 
   9: <xsl:if test="not(normalize-space(@Lat) = '' and normalize-space(@Long) = '')"> 
  10: AddPin(<xsl:value-of select="@Lat" />, 
  11: <xsl:value-of select="@Long" />, 
  12: null, 
  13: "<xsl:value-of select="@Title" />", 
  14: "<xsl:value-of select="@Description"/>"); 
  15: </xsl:if> 
  16: </xsl:for-each> 
  17: <xsl:text disable-output-escaping="yes"><![CDATA[ 
  18: } 
  19: </script> 
  20: ]]></xsl:text> 
  21: </xsl:template> 

 

Find the following code in the Data View

    <xsl:template name="dvt_1"> <xsl:variable name="dvt_StyleName">Table</xsl:variable>     <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>

And insert the following immediately after

<xsl:call-template name="AddMapPins"><xsl:with-param name="Rows" select="$Rows"/> </xsl:call-template>

 

And Viola you are all set to see virtual earth in sharePoint.

Reusing SPSchedulePicker Control

SchedulePicker Class (in the Microsoft.SharePoint.WebControls namespace) is very handy when it comes to creating a SPSchedule. Particularly when creating application pages that handle the scheduling of custom timer jobs.

So instead of creating your own dropdown’s with all the days of the week and all the possible times and converting to those the proper schedule types that SharePoint has (SPMinuteSchedule, SPHourlySchedule, etc) you can use a control that does everything for you. The only thing you need to specify what the options are that an user can select from by setting the properties.

So using this line in my application page:

<%@ Register TagPrefix="wssuc" TagName="SchedulePicker" src="~/_controltemplates/SchedulePicker.ascx" %>
<wssuc:SchedulePicker id="SchedulerAction" Weekly="True" Monthly="True" Enabled="True" EnableStateView="True" runat="server"/>

Generates this result:

clip_image002

So if you only want to have the options to schedule during the week you remove the “Monthly=’True’” property and that part of the control will not be visible.

The only code you have to write to schedule your timer job based on what you configure in this control is this:

//reference the control like this

protected SchedulePicker Scheduler

private void InstallTimerJob()

{

CustomTimerJob customTimerJob = new CustomTimerJob("MyCustomJob", webApplication);

customTimerJob.Schedule = Scheduler.Schedule;

customTimerJob.Update();

}

And the code to set the control with the configured values is like this:

protected override void OnLoadComplete(EventArgs e)

{

SPWebApplication webApplication = webApplicationSelector.CurrentItem;

if (!Page.IsPostBack)

{

foreach (SPJobDefinition job in webApplication.JobDefinitions)

{

if (job.Name == "MyCustomJob" )

{

Scheduler.ScheduleString = job.Schedule.ToString();

}

}

}

}

 

You can also refer to the blog article: http://community.zevenseas.com/Blogs/Robin/archive/2009/01/18/using-the-schedulepicker.aspx

How to - Increase timeout value of a SharePoint workflow

I was recently working on an issue where customer had a workflow which used to fail and error out after the workflow would execute for some tasks. On deeper analysis I found that the workflow was creating 5000 tasks and it is then when the workflow errored out. On checking the Workflow and ULS traces I found the error message as (have to say least helpful):

<Error Message>

- Workflow Infrastructure 72fg High Error in persisting
workflow: System.Transactions.TransactionAbortedException: The transaction has
aborted. ---> System.TimeoutException: Transaction Timeout --- End of inner
exception stack trace --- at
System.Transactions.TransactionStateAborted.CreateAbortingClone(InternalTransaction
tx).
- System.Workflow.Runtime.Hosting Error: 0 : DefaultWorkflowCommitWorkBatchService
caught exception from commitWorkBatchCallback:
System.Transactions.TransactionAbortedException: The transaction has aborted. --->
System.TimeoutException: Transaction Timeout

</Error Message>

It somehow came in my mind that it has to be that the workflow was timing out. On googling I found a way to increase the time out value fo the workflow from the default value – 1 min to 30 mins. Do this, in the web.config file of the web application we need to set the default
value to something more ( in this case its set to 30 mins )
<configuration>
<system.transactions>
<defaultSettings timeout="00:30:00" />
</system.transactions>
</configuration>

 

And viola this worked.

Modify a date-time value field in Event Handlers – Through AfterProperties

Recently I had a requirement from my customer where it was required to update a date time field through the event handlers and that had to be done in the asynchronous events. Well you may think that this is very simple and setting it with specific date time format would work. But sadly the fields accept a special format which way beyond the formats people normally know.

I tried all the below ways and it failed in all the formats. It will give the following error:

Invalid date/time value. A date/time field contains invalid data. Please check the
value and try again.

properties.AfterProperties["ApprovedTimeStamp"] = DateTime.Now ;
properties.AfterProperties["ApprovedTimeStamp"] = DateTime.Now.ToString(); //
properties.AfterProperties["ApprovedTimeStamp"] = new DateTime(2008, 11, 08) ;
properties.AfterProperties["ApprovedTimeStamp"] = DateTime.Now.ToLongDateString()
properties.AfterProperties["ApprovedTimeStamp"] = DateTime.Now.ToShortDateString()

The correct way to do so is in one of the ways

properties.AfterProperties["ApprovedTimeStamp"] =
DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ"); // Works Fine
properties.AfterProperties["ApprovedTimeStamp"] =
Microsoft.SharePoint.Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(Dat
eTime.Now); // Works Fine

Application Definition Designer – New Name for BDC Editor Tool

I recently downloaded the new SharePoint SDK – 1.5 (April 2009 version) and found that the BDC editor tool was renamed to Application Definition Designer Tool. After installing i explored the various options and found no change in the functionality.

So effectively its just a name change. :)

Page Editing Toolbar - Problem with submit for approval node in the quick access

You need to customize the CustomQuickAccess.xml to achieve the functionality. Curiously, the functionality works well when you click the button – “Submit for approval..”  under the workflow tab. So the idea was to somehow replace the original button with a new button which has the same functionality as that of “Submit for Approval …”.

Following is the content of the customquickaccess.xml which will delete the non working node and will add the working node in the quick access area.

<?xml version="1.0" encoding="utf-8" ?>

<Console>

      <references>

            <reference TagPrefix="cms" assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,

PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions" />

      </references>

      <structure>

            <ConsoleNode Action="cms:PublishWithCommentAction" ID="saPublishWithComment" Sequence="610" ConfigMenu="Add"/>

            <ConsoleNode ConfigMenu="Delete" ChangedNodeID="qaPublish" />

      </structure>

</Console>

In the above CustomQuickaccess.xml you can find that we are adding the cms:PublishWithCommentAction through ConfigMenu=Add option and deleting the “qaPublish” node through ConfigMenu=Delete which is cms:PublishAction console node existing in the quickaccess.xml. This is how we need to override the OOB Quick area nodes. In the same way you can customize the EditingMenu and Site Action nodes.

Once you save and publish the customquickaccess.xml file the problematic console node vanishes and the new console node for “SubmitForApproval” appears in the quickaccess which works fine for all the workflows irrespective of the associations.

So the conclusion is that the console node with action cms:PublishAction is a problematic node when you have custom workflows (with ASPX page association) attached to the pages library. The above mentioned trick is the work around or resolution and you can dig deeper on the above mentioned classes (Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions.PublishWithCommentAction and Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions.PublishtAction) to check the root cause for this behavior.

 

Posted by varunmalhotra | 1 Comments

Save files from SharePoint document library to File System

Normally you would get requirements for uploading documents to the document library, but you would rarely find the requirement to download all the documents from SharePoint List to the the file system. As such the functionality is pretty easy to implement -> Read the Byte stream of the document library item and then Write the stream using any writer.

Lets add a bit of fun in this. Consider the document library has documents at multiple levels and folders at each levels which have documents and further folders in them.

The only solution for this is to use the Cool Principle of "RECURSION". Below is the code I have written.

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace CA_FileCopyFromDocLibToserver
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Please enter the url of the site collection (Press Enter after typing)");
                SPSite site = new SPSite(Console.ReadLine());

                SPWeb web = site.AllWebs[""];

                Console.WriteLine("Please enter the Document Library Name (Press Enter after typing)");
                string docLibName = Console.ReadLine();

                Console.WriteLine("Please enter the Target Url on the machine (Press Enter after typing)");
                string TargetUrl = Console.ReadLine();

                CopyFolder(web, docLibName, TargetUrl);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error --- " + ex.Message);
                Console.ReadLine();
            }
            Console.WriteLine("Files Copied Succesfully.");
            Console.ReadLine();
        }

        static public void CopyFolder(SPWeb web, string sourceFolder, string destFolder)
        {
            SPFolder spSourceFolder = web.GetFolder(sourceFolder);
            
            if (!Directory.Exists(destFolder))
                Directory.CreateDirectory(destFolder);
            SPFileCollection files = spSourceFolder.Files;
            foreach (SPFile file in files)
            {
                byte[] b = file.OpenBinary();
                FileStream fs = new FileStream(destFolder + "\\" + file.Name, FileMode.Create, FileAccess.ReadWrite);
                BinaryWriter bw = new BinaryWriter(fs);
                bw.Write(b);
                bw.Close();
            }

            SPFolderCollection folders = spSourceFolder.SubFolders;
            
            foreach (SPFolder folder in folders)
            {
                
                string name = folder.Name;
                string dest = Path.Combine(destFolder, name);
                if (folder.Name != "Forms")
                {
                    CopyFolder(web, folder.Url, dest);
                }
            }
        }

    }
}

One small important thing to remember here is that when you iterate recursively through the list of folders of the SharePoint Document library it will also pick up the Forms folder which has OOB files such newform.aspx, etc. which may not be required to be downloaded as it is not part of the content.

 

Delete items of SharePoint recycle bin efficiently using - SPRecycleBinQuery

Recently I had a strange query from one of my clients. The client had the OOB SharePoint recycle bin. Somehow the number of items in his recycle bin became more than 500,000. So as we know SharePoint has a scheduled cleanup job that can be scheduled to run once a day, that cleans up items that go beyond the retention period. So now when this scheduled job runs, it tries to delete around 10,000 items and hangs the SharePoint server. On digging it further I found that the SharePoint locks the database tables and hence hangs the SharePoint Server. This happens as the batch process is trying to delete 10,000 in one go.

In normal scenarios we would try to either delete items one by one – by iterating through all the items of the list and call the item’s delete method or use the deleteall() method. Both the ways have problems as they are very inefficient and memory consuming. Technique one would load the entire item collection in memory and hence inefficient and technique 2 for deleteall would behave in the same manner as the Scheduled job.

So the only solution would be to query the Recycle Bin to get say 1000 items and then delete the items one by one. But there is one more challenge here.  Normally the data in SharePoint (stored in the DB) is displayed/represented as a List and you can use SPQuery API to query the lists. Recycle Bin is of special type as the recycle bin is a page that brings data directly from the Database so SPQuery can’t be used.

On researching in the SDK I found a useful API - SPRecycleBinQuery (With very little documentation on usage). This API queries the Recycle Bin and you can set the maximum row limit hence it is very efficient as well. Following code can be used to delete the items in such a scenario:

<Code>

           try

            {

                Console.WriteLine("Please specify the Site Collection Url: (Please click enter after typing)");

 

                SPSite site = new SPSite(Console.ReadLine());

                SPWeb web = site.RootWeb;

               

                SPRecycleBinQuery q = new SPRecycleBinQuery();

 

                Console.WriteLine("Specify the number of records to be deleted (Click enter after typing)");

                q.RowLimit = Int32.Parse(Console.ReadLine());

                q.OrderBy = SPRecycleBinOrderBy.Default;

 

                SPRecycleBinItemCollection itemColl = web.GetRecycleBinItems(q);

                foreach (SPRecycleBinItem item in itemColl)

                {

                    Guid[] id = new Guid[1];

                    id[0] = item.ID;

                    itemColl.Delete(id);

                }

                Console.WriteLine("Deletion was successful (Click enter to exit)");

                Console.ReadLine();

            }

            catch (Exception ex)

            {

                Console.WriteLine("Exception Message" + ex.Message);

            }

</Code>

Posted by varunmalhotra | 1 Comments
Filed under:

How to get users under a subsite/area in WSS 2.0/SPS 2003?

Although it seems a very basic question, but I thought of blogging this as it is not as easy as it is in MOSS 2007. Normally our approach would be to use the SPWeb object to represent the sub site and then iterate through the list of users in the SPWeb object. Surprise! Surprise! This doesn’t give you the correct list and if you try to go to the sub site in the UI and check the users that would be entirely different.

So to get the sub site/area users the following code can be used.

<Code>

TopologyManager tm = new TopologyManager();

                PortalSite ps = tm.PortalSites[new Uri("http://Moss12:1000")];

                Microsoft.SharePoint.Portal.PortalContext ctx = Microsoft.SharePoint.Portal.PortalApplication.GetContext(ps);

                Guid NewsGuid = AreaManager.GetSystemAreaGuid(ctx, SystemArea.Topics);

 

                PermissionCollection pc = SecurityManager.ManageAreaSecurity(ctx, NewsGuid);

                foreach (Permission pm in pc)

                {

                    User usr = pm.Member as User;

                    Role role = pm.Member as Role;

                    SPRole roles = pm.Member as SPRole;

                    if (usr != null)

                    {

                        Console.WriteLine("User Email: " + usr.Name);

                    }

                    else

                    {

                        Console.WriteLine("Role name: " + role.Name);

                        Console.WriteLine(pm.PortalMask);

                    }

                }

</Code>

 

 

Fragment Caching for user controls doesn’t work in SharePoint if using SPD.

I recently had a strange requirement or should I say an issue where the client had created a user control in SharePoint and was trying to use Fragment Caching on the user control. But somehow the fragment caching wasn’t working. And the client was asking whether SharePoint actually Supports Fragment Caching or not?

On further investigation I found that he was adding the user control through SPD. After a couple of tests I found the following reason on why Fragment Caching wouldn’t work for SPD added User Controls.

To understand the reason, I would like to touch base on how output caching/fragment caching works. Whenever you ask the control to be cached the output of the control is stored on the server. So that any request that comes for the page containing the control, the output is rendered straight from the cache and the code involved with the control is not executed again. The cache in this case is the server cache (Output saved on server). This would continue till the time the cache is invalidated.

Now let’s take a look how SPD works. Any customization which you do using SPD is stored in the database. So every request for this page, would bring the customized content from the database.

Now let’s combine the two – SPD and caching. When you add the cached user controls from SPD, the user controls are added to the database. So even though you add the entries in SPD, the changes are not added to the file but to the database. So when the page is requested, the contents and particularly, the cached user controls are brought from the database. And this happens every time irrespective of the fact that output caching has been enabled for the controls.

I researched and found that for controls that are added through SPD, since content comes from DB, no output is stored in the Server Cache and hence fragment caching doesn’t work when you use SPD. So the problem is not with SharePoint, not even with the SPD, but with the way SPD works which cannot be combined with output caching.

The workaround for this issue is to use one of the following:

1.       If you want to show the user control on all pages:- This can be done by adding the user controls to the master page.  So take the following steps to achieve this:

a.       Check the master page being used by the site – CustomDefault.master.

b.      Open the CustomDefault.master - master page in notepad or visual studio but not SPD. Add the two register tags and the instances of the user controls.

Register Tags:

<%@ Register TagPrefix="wssuc" TagName="WellSet" src="~/_controltemplates/Cached.ascx" %>

<%@ Register TagPrefix="wssuc" TagName="UnCached" src="~/_controltemplates/UnCached.ascx" %>

 

Add the instances somewhere after the <head> tag.

<wssuc:Cached id="IdWellSet" runat="server"></wssuc:Cached>

<wssuc:UnCached id="IdWellSet1" runat="server"></wssuc:UnCached>

 

c.       And that’s it and you are all set to use the user controls.

d.      Now open the site and saw the output of the user controls and Output Caching works perfectly for the Cached User Control.

 

2.       If you want to show the user controls on one of the pages or a selected few:  The best and easy way to use this is to create a custom web part and add that web part to the gallery of the site. From the gallery you can go ahead and add web part to the selected page/s.

 

 

SPWebApplication.Delete – Deletes Web Application – Myth Busted!!

If someone asks you, how to delete a WebApplication from the code API, the simple answer is to use – SPWebApplication.Delete(). So logically, you would expects that when you run this command it would remove the web application from central admin (Config DB) and also remove the associated contents – Content DB and IIS WeB Application, App Pool and the home directory. (That’s the myth I am talking about)

But on close analysis found that this command only removes the Web application from the config db but rest of the associated resources remain behind (as zombies – Can’t be used). To completely delete the associated resources you need to explicitly call the commands for deleting the content DB and the IIS resources.

On research I found the sample code below works perfectly.

   1: SPWebApplication wa = SPWebApplication.Lookup(new Uri("<WebApp Url with port number>"));
   2:             
   3:             SPContentDatabaseCollection dbColl = wa.ContentDatabases;
   4:             foreach (SPContentDatabase db in dbColl)
   5:             {
   6:                 db.Unprovision();
   7:             }
   8:  
   9:             wa.Delete();
  10:             wa.Unprovision();

The code SPContentDataBase.UnProvision() deletes the content dbs and the command SPWebApplication.UnProvision() deletes the IIS web site, app pool and the root folder.

The reason why this was missed in the command SPWebApplication.Delete(), I guess was to give you more granularity and power to control different actions.

After Properties go blank in ItemUpdated event for Office 2007 Documents

Event Handlers in MOSS have come a long way since their inception in SharePoint 2003. In MOSS, event handlers expose a pair of properties which can be used for easing quite complex functionality. Yes, I am talking about the pair – AfterProperties and BeforeProperties. I am sure you would be knowing the basic functionality of these properties. You can use the AfterProperties to get values which are set after the evnt executes and BeforeProperties to get the values before the event executes. And I also know that the web is full of articles which talk about bugs related to these pairs.

I am not going to go into the known issues with the Pair of properties. (I am saving it for another blog post)

 

So lets come to the main issue. I had a requirement where we wanted to set default values of custom columns of a list. So the normal practice to achieve such functionality is to set the default values in itemAdding event of the fields using the afterProperties property. (Pretty Normal stuff, Right) This works like a breeze for all type of documents, but strangely the default values were not being set for the Office 2007 documents. On debugging I found that AfterProperties were getting blank between the ItemUpdating and  ItemUpdated event. In case you change the values of the fields from the UI, it worked. But for the default values these AfterProperties were getting blank and hence no value was being saved.

 

On deeper analysis, I found that the Office 2007 document parser is to be blamed here. The document parser for Office 2007 allows the default value to be set, only if you declare the default value at the time of column creation. (Either through UI or OM code)

 

So the possible workarounds for this problem are:-

  1. Set the default value at the time of field creation:

This can be achieved from UI or through code. I am writing a sample code for a multichoice field:

<Code>

 

SPFieldMultiChoice choice = lib.Fields[choiceField] as SPFieldMultiChoice;

 

            choice.Choices.Add("North");

            choice.Choices.Add("South");

            choice.Choices.Add("East");

            choice.Choices.Add("West");

            choice.DefaultValue = ";#North;#West;#";

            choice.Update();

 

</Code>

Pros

Cleaner approach, Works for all types of docs

Cons

If you already have a list with a lot of documents, this can be used only for future items

  1. Set the default value from the ItemUpdated event.

As I specified above the AfterValues are being cleared just before the itemUpdated event, so why not reset the values in ItemUpdated event. The following code can be used for reference.

<Code>

public override void ItemUpdated(SPItemEventProperties properties)

            {

                  base.ItemUpdated(properties);

 

                  if (properties.ListItem["Region"] == null)

                  {

                        properties.ListItem["Region"] = ";#North;#West;#";

                        this.DisableEventFiring();

                        properties.ListItem.Update();

                        this.EnableEventFiring();

                  }

 

            }

 

     </Code>

Pros:

Works well for all situations

Cons:

Works erratically for lists with checkIn and checkout facility.

 

 

Add users of an AD Group to the SharePoint Site

Recently, I had a requirement from my Customer which required to add an AD groups’s user to the SharePoint site. As you know, if you try to add the SharePoint group directly to the SharePoint site, it would simply create the group but wouldn’t add the users. So you need to explicitly add users to the site. Customer wanted me to provide some utility that could automate this process.

The key to this solution involves Querying the AD group to get the users that belong to the AD group and then adding these users to the SharePoint Site. To ge the list of the users from AD, Asp.Net provides the namespace - System.DirectoryServices. The namespace exposes a number of classes that allow you to query the AD. BElow is the sample code to get a list of users from AD:

   1: public static List<UserInfo> PopulateUserInfoFromADGroup(string GroupId)
   2:         {
   3:             //PLease set the GC or LDAP server name
   4:             string domain = "GC://corp.emailserver.com";
   5:             System.DirectoryServices.DirectoryEntry entry = new DirectoryEntry(domain);
   6:  
   7:             DirectorySearcher adSearcher = new DirectorySearcher(entry);
   8:             adSearcher.SearchScope = SearchScope.Subtree;
   9:  
  10:             // Please remember to have change the below string
  11:             // OU=Distribution Lists,DC=domain,DC=corp,DC=microsoft,DC=com
  12:             // The string was for our domain so you need to change the domain string to your domain string
  13:             //It mostly is of the format abc.abc.abc.com so have four DC variables with value as 
  14:             // DC=abc,DC=abc,DC=abc,DC=com
  15:             adSearcher.Filter = "(&(objectClass=user)(memberOf=CN=" + GroupId + ",OU=Distribution Lists,DC=domain,DC=corp,DC=microsoft,DC=com))";
  16:             SearchResultCollection oResult = adSearcher.FindAll();
  17:  
  18:             List<UserInfo> userList = new List<UserInfo>();
  19:  
  20:             if (oResult != null)
  21:             {
  22:                 foreach (SearchResult result in oResult)
  23:                 {
  24:                     UserInfo user = new UserInfo();
  25:                     ResultPropertyValueCollection propColl = result.Properties["sAMAccountName"];
  26:                     ResultPropertyValueCollection propCollName = result.Properties["displayname"];
  27:                     ResultPropertyValueCollection propCollMail = result.Properties["mail"];
  28:                     ResultPropertyValueCollection propCollDomain = result.Properties["msds-SourceObjectdn"];
  29:                     for (int i = 0; i < propColl.Count; i++)
  30:                     {
  31:                         user.UserEmail = propCollMail[i].ToString();
  32:                         user.userLoginName = propColl[i].ToString();
  33:                         user.USerName = propCollName[i].ToString();
  34:  
  35:                         //Get the Domain Name
  36:                         string DomainName = propCollDomain[i].ToString();
  37:                         Char comma = ',';
  38:                         string[] test = DomainName.Split(comma);
  39:                         user.DomainName = test[2].Remove(0, 3);
  40:                         userList.Add(user);
  41:                     }
  42:                 }
  43:             }
  44:  
  45:             return userList;
  46:         }

 

After you have got the list of users from AD, you can simply add the Users to the SharePoint site.

   1: static void Main(string[] args)
   2:         {
   3:             string strSiteCollectionUrl, strADGroupName;
   4:  
   5:             Console.WriteLine("Enter Site Collection Url: (Press Enter after entering the url)");
   6:             strSiteCollectionUrl = Console.ReadLine();
   7:  
   8:  
   9:             Console.WriteLine("Enter Friendly AD Group Name: (Press Enter after entering the Name)");
  10:             strADGroupName = Console.ReadLine();
  11:  
  12:             List<UserInfo> userList = new List<UserInfo>();
  13:             userList = PopulateUserInfoFromADGroup(strADGroupName);
  14:  
  15:             SPSite site = new SPSite(strSiteCollectionUrl);
  16:             SPWeb web = site.OpenWeb();
  17:  
  18:             SPGroup AddUserGroup;
  19:             //Check if Group Exists
  20:             
  21:             
  22:             foreach (UserInfo user in userList)
  23:             {
  24:                 SPRoleDefinitionCollection roleDefinitions = web.RoleDefinitions;
  25:                 SPRoleAssignmentCollection roleAssignments = web.RoleAssignments;
  26:                 SPRoleAssignment roleAssignment = new SPRoleAssignment(user.DomainName + "\\" + user.userLoginName, user.UserEmail, user.USerName, "");
  27:                 SPRoleDefinitionBindingCollection roleDefBindings = roleAssignment.RoleDefinitionBindings;
  28:                 roleDefBindings.Add(roleDefinitions.GetByType(SPRoleType.Contributor));
  29:                 roleAssignments.Add(roleAssignment);
  30:                 SPUser newUser = web.SiteUsers[user.DomainName + "\\" + user.userLoginName];
  31:                 try
  32:                 {
  33:                     AddUserGroup = web.SiteGroups[strADGroupName];
  34:                 }
  35:                 catch
  36:                 {
  37:                     web.SiteGroups.Add(strADGroupName,newUser,newUser,"");
  38:                 }
  39:                 AddUserGroup = web.SiteGroups[strADGroupName];
  40:                 AddUserGroup.AddUser(user.DomainName + "\\" + user.userLoginName, user.UserEmail, user.USerName, "");
  41:             }
  42:             
  43:         }    
  44:  
  45: /// <summary>
  46:     /// Class with User details
  47:     /// </summary>
  48:     public class UserInfo
  49:     {
  50:         private string _userLoginName;
  51:  
  52:         public string userLoginName
  53:         {
  54:             get { return _userLoginName; }
  55:             set { _userLoginName = value; }
  56:         }
  57:  
  58:         private string _userName;
  59:  
  60:         public string USerName
  61:         {
  62:             get { return _userName; }
  63:             set { _userName = value; }
  64:         }
  65:  
  66:         private string _userEmail;
  67:  
  68:         public string UserEmail
  69:         {
  70:             get { return _userEmail; }
  71:             set { _userEmail = value; }
  72:         }
  73:  
  74:         private string _domainName;
  75:  
  76:         public string DomainName
  77:         {
  78:             get { return _domainName; }
  79:             set { _domainName = value; }
  80:         }
  81:     
  82:     
  83:  
  84:     }
More Posts Next page »
 
Page view tracker