Mann Software
Mann Software > SharePoint and the Office System
HttpContext in Event Receivers

A number of people (notably here, here and here) have written about ways to get a value for HttpContext.Current inside a SharePoint event receiver. They all correctly note that it only works for synchronous events, which makes perfect sense. What they don't mention is that it does NOT work for all synchronous events – notably ItemDeleting. HttpContext.Current is always null in ItemDeleting.

SharePoint Conference Sessions

A preliminary list of session titles has been announced for the SharePoint conference in October. There will be many more to come, but here is a sneak peek:

  • SharePoint 2010 Overview and What's New
  • Upgrading to SharePoint 2010
  • SharePoint 2010 Capacity and Performance Planning
  • SharePoint 2010 Security and Identity Management: What's New
  • Visual Studio 2010 Tools for Office Development
  • SharePoint 2010 Ribbon, ECMAScript and Dialog Framework Development
  • Developing with REST and LINQ in SharePoint 2010
  • Upgrading SharePoint Server 2007 Code to SharePoint 2010
  • Building Composite Applications with the Microsoft Application Platform
  • What's New in Business Connectivity Services (The Business Data Catalog Evolves!)
  • FAST Search for SharePoint – Capabilities Deep Dive
  • Advanced Dashboard Creation with Performance Point Services for SharePoint 2010
  • Overview of Visio and Visio Services for SharePoint 2010
  • SharePoint 2010 Web Content Management Deep-Dive
  • If You Build It, They Will Come – Driving End User Adoption

Looking good…can't wait until October…

 

Dave

User Controls NOT in the ControlTemplates folder

A recommendation you'll see floating about the web is to put any custom user controls you create into the CONTROLTEMPLATES directory. However, it's like placing files into LAYOUTS – it's better to be in your own sub-folder. For RenderingTemplates though SharePoint apparently looks for them ONLY in the CONTROLTEMPLATES folder. So how can we get them into a subfolder?

Basically, you just need to put them there (as part of your Solution deployment, of course).

The web.config file for SharePoint contains a line that registers anything in or under the CONTROLTEMPLATES folder as "safe":

<SafeControl Src="~/_controltemplates/*" IncludeSubFolders="True" Safe="True" AllowRemoteDesigner="True" />

So we don't need a new SafeControl entry. You will get an error in your ULS logs:

Control template "MyFieldControl" does not exist.

But you can safely ignore this…

One other problem you'll have if you're doing this in a custom field (and perhaps other locations – I haven't tested) is verifying that your custom UI controls exist. Since SharePoint "can't find" your template, you'll have a problem. Most sample custom fields you'll find floating around the web contain code something like this in the CreateChildControls method of the control display page:

ddlLookup = (DropDownList)(TemplateContainer.FindControl("ddlLookup"));

if (ddlLookup == null)

{

throw new ArgumentException("ddlLookup is null. Corrupted

    LookupControl.ascx file.");

}

All you need to do is explicitly load the field's user control, load the TemplateContainer control (which is your RenderingTemplate) and then search recursively for your UI control by ID:

TemplateContainer templateContainer = new TemplateContainer();

Control ctl = (Control)Page.LoadControl

    ("~/_controltemplates/MyField/ MyField_UserDisplay.ascx");

templateContainer.Template = ((RenderingTemplate)ctl.Controls[0]).Template;

Controls.Add(templateContainer);

 

ddlLookup = (DropDownList)FindChildControlRecursive(this, "ddlLookup");

if (this.phDynamicControls == null)

{

throw new ArgumentException("ddlLookup is null. Corrupted

    LookupControl.ascx file.);

}

Note: the FindChildControlRecursive method simply iterates through the control hierarchy starting at the specified control (first parameter – this) looking for a control with the ID specified in the second parameter (ddlLookup).

 

Changing the MasterPage for Publishing Sites

I knew this at one time, but my Swiss cheese brain apparently forgot it at some point…

Simply changing <SPWeb>.MasterURL works perfect for WSS and Application pages, etc. However, it's only half of the answer when you toss publishing and layout pages into the mix. In that case, you also need to set <SPWeb>.CustomMasterURL:

site.MasterUrl = myNewMasterURL;

site.CustomMasterUrl = myNewMasterURL;

site.Update();

Dave

Content Type Inheritance

When creating a Content Type declaratively, you may not add any site columns through a FieldRef. In other words, your element file may look like this:

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

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

<ContentType

ID="0x0100D527CCABB5FB4bd3A34B2EB6059F1E0502"

Name="My CT"

Description="CT #1"

Version="0"

Group="My Content Types" >

</ContentType>

 

</Elements>

It's a perfectly legal element file to define a Content Type and you may be thinking that you'll attach columns later programmatically. However, if you do this then your Content Type will not inherit any columns from its parent Content Type.

To overcome this, assuming you need to, simply re-add the default Title column in a <FieldRef> and all of the other columns in the hierarchy of your Content Type will be inherited as well. Your element file will now look like this:

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

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

<ContentType

ID="0x0100D527CCABB5FB4bd3A34B2EB6059F1E0502"

Name="My CT"

Description="CT #1"

Version="0"

Group="My Content Types" >

 

<FieldRefs>

<!-- re-add the built-in WSS Title column so other inherited columns are inherited properly-->

<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"

Name="Title" DisplayName="Title" Sealed="TRUE" Required="TRUE" />

</FieldRefs>

</ContentType>

 

</Elements>

Now, all of the columns defined further up the hierarchy, including the Title column, will be inherited properly.

-Dave

AddFieldAsXml

If you're adding a field via the AddFieldAsXML method, remember that XML is case sensitive. Required='true' is not the same as Required='TRUE'. The latter is what is required in order to actually make your field required.

Preventing Duplicate Items

I've answered a few questions regarding this on the MS Forums and it was also something that we needed in my current project. I thought I would post something here in case anybody else needs it.

Here's the scenario: you want to make sure that items added to a list are unique, or at least that a certain column is unique – perhaps some type of ID column, or some other key value that cannot be duplicated or else bad things happen. Here's what you need to do:

  1. Add an Event handler to the list. Here are a few good postings that cover the basics of event handlers:

http://www.sharepoint-tips.com/2006/08/bad-news-synchronous-list-events-bug.html

http://blah.winsmarts.com/2006-7-Sharepoint_2007__List_Events_Practical_Example__Creating_a_rigged_survey.aspx

  1. Add the following code for the ItemAdding event:

    public override void ItemAdding(SPItemEventProperties properties)

    {

    try

    {

    SPList list = properties.OpenWeb().Lists[properties.ListId];

    string sConcernName = properties.AfterProperties["Title"].ToString();

    SPQuery query = new SPQuery();

    query.Query = @"<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>" + sConcernName + "</Value></FieldRef></Eq></Where>";

    SPListItemCollection items = list.GetItems(query);

    if (items.Count > 0)

    {

    properties.Cancel = true;

    properties.ErrorMessage = "A Concern with that Name already exists";

    }

    }

    catch (Exception ex)

    {

    properties.Cancel = true;

    properties.ErrorMessage = ex.Message;

    }

    }

  2. You'll need to edit the query string to work off of whichever column or columns you need to be unique, but that's just some simple CAML
  3. Register the event handler (see the articles from #1 above for help)
  4. Test. If you've lived a righteous life, you will not be able to add columns with duplicate titles (at least for the example above). Instead, you'll get the standard SharePoint error page with your custom message.

Hope that helps somebody.

Custom Workflow Status

A question on the SharePoint Forums prompted this write up...

A little known feature of SharePoint workflows is the ability to establish your own "Status" values for your workflow. By default, the following enum lists the values for SPWorkflowStatus:

 

public enum SPWorkflowStatus

{

Completed = 5,

ErrorOccurred = 3,

ErrorOccurredRetrying = 7,

FailedOnStart = 1,

FailedOnStartRetrying = 6,

InProgress = 2,

Max = 15,

NotStarted = 0,

StoppedByUser = 4,

ViewQueryOverflow = 8

}

The only one that is not really clear is "StoppedByUser" - this typically means that the workflow was Terminated through the UI.

The cool part of this is that you can specify your own values for a Status by adding entries to the workflow.xml file:

 

<MetaData>

<ExtendedStatusColumnValues>

<StatusColumnValue>Custom Status 1</StatusColumnValue>

<StatusColumnValue>Custom Status 2</StatusColumnValue>

</ExtendedStatusColumnValues>

</MetaData>

 

To set your workflow to a custom status, you need to add a State activity (the one from the SharePoint group, not the one that is for State Machines) and set it's State property to the integer value for the status you want. For the default values from the SPWorkflowStatus enumeration, the proper integer value is easy. For your custom statuses, you need to understand how your custom statuses are handled. Basically, the SPWorkflowStatus.Max value is equal to 15, as shown above, and this is where your custom statuses are added, treating them as a 0-based list. So, the first new value you add in workflow.xml has an integer value of 15 (SPWorkflowStatus.Max + 0), the second 16 (SPWorkflowStatus.Max + 1), etc.

The last part, and the actual question that prompted this from the forums is how do I retrieve the status? The trick here is that the status is a value added to the SPListItem the workflow is running on. Therefore, the following code will retrieve you an integer value (which follows the numbering scheme from above) for the value of your workflow on a given SPListItem:

SPListItem itm = <code to retrieve the SPListItem you are interested in>;

Int iWorkflowStatus = itm["<Name_of_Your_Workflow>"];

That's it. You can now make and use the status values you need for your workflow.

SPWebConfigModification

Just a quick note here.

When you are adding a web.config entry via the SPWebConfigModification class, you need to specify a Name for the modification. Typically, it will look something like this: add[@name=\"CustomSiteMapProvider\"] . It is an XPath expression to uniquely identify the modification. The tricky part is that this name must match the name= parameter in the string added to the web.config (assuming you have one).

In other words, this is OK:

Name= add[@name=\"CustomSiteMapProvider\"]

Value= <add name='CustomSiteMapProvider' type='MyNS.MyClass, MyDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxx' description='Custom site map provider' NavigationType='Global' />

 

 

Whereas this is NOT OK:

Name= add[@name=\"CustomSiteMapProvider\"]

Value= <add name='MyNewSiteMapProvider' type='MyNS.MyClass, MyDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxx' description='Custom site map provider' NavigationType='Global' />

Note the difference in the name attribute in the Value and the Name of the modification.

This will become a problem when you attempt to REMOVE the entry. You will not be able to.

Wiki Processing

This is going to be a work in progress for a little bit because I'm still figuring out bits and pieces of this, and likely will be for a little while yet. I'll come back and edit this post as necessary.

This is a result of some more work I'm doing on the Community Kit for SharePoint. I'm revisiting our basic design for rendering wiki pages to see if it can be streamlined a bit, or made more versatile. At this point, I'm afraid the answer is "No" on both counts. With that said, I'll attempt to save you some time - if you want to understand how SharePoint wikis work in order to extend their base processing you can stop here. This whole article will turn out to be nothing more than an intellectual exercise. To the best of my knowledge right now, Microsoft kind of closed the door on us. We are extremely limited in the options we have for extending the default wiki functionality. Instead, we have to rip and replace to get the additional functionality we need. I'll cover that in a later blog post.

Nonetheless, it will be a fairly short article, and contains some interesting tidbits, so feel free to stick around. I'll have another post in a week or two that walks through the CKS Wiki and explains how we overcame some of the limitations in the default wiki.

Let's get started...

First, a few interesting tidbits:

  1. When you navigate to a wiki site in WSS, you are shown the "Home" page of the wiki instead of a regular default.aspx. This little bit of wizardry is accomplished by setting the (SPWeb).RootFolder.WelcomePage property for your site to a different URL, in this case the home wiki page. You can use this for any type of site. So, for example, if you wanted people to be taken to the AllItems view of a site's document library any time they visited http://site_name, you could use code similar to this:

    SPSite sitecoll = new SPSite("http://site_name");

    SPWeb site = sitecoll.OpenWeb();

    SPFolder fldr = site.RootFolder;

    fldr.WelcomePage = "Shared%20Documents/Forms/AllItems.aspx";

    fldr.Update();

    site.Update();

    site.Dispose();

    sitecoll.Dispose();

    This works for any page in the site. The default.aspx page is still available if they navigate directly to it, but it is no longer the "homepage" for the site.

  1. All wiki pages are based off of the wkpstd.aspx page in the 12\DOCUMENTEMPLATES folder. We'll cover this more later, but if you simply want to make minor changes to the wiki pages, you can edit that page. All of the usual warnings apply about editing the out of the box SharePoint files, but unfortunately, there is no way to tell the default wiki to use a different page as its template. As you'll see, we do use a different template in the enhanced wiki, but that's because we largely rip out whole portions of this process and replace them with our own.

Now let's get down to business. The process of handling wiki pages and wiki editing is remarkably simple, once you wade through it all and look at all of the moving parts. As I said, though, you have to look at this as a nice intellectual exercise because we cannot make use of much of it when we look to extend the wiki.

It all starts when we want to create a new wiki page. We can do this by either clicking the "New" button in the list or by clicking on a [[wiki link]] in an existing page, shown below:

Either way, SharePoint loads the CreateWebPage.aspx page from the Layouts directory.

CreateWebPage.aspx

The CreateWebPage page is nothing special technically, but it is part of the key to wiki functionality in SharePoint. If you're not familiar, here's what it looks like:

 

If you look at the code, you'll see a pretty standard layouts page. The important parts of the page (the Name and Wiki Content fields) are displayed with a few lines of markup:

As I said before, it's nothing to write home about, technically, at least not for the presentation so far. One interesting bit happens when you click the Create button. At that point, the click event handler comes into play. If you want to take a look at this, you need to open up the Microsoft.SharePoint.ApplicationPages from the app_bin folder of your web application (not the ...12\ISAPI folder) in Reflector. Navigate through to Microsoft.SharePoint.ApplicationPages.dll\ Microsoft.SharePoint.ApplicationPages\CreateWebPage and then take a look at the SubmitBtn_Click method.

This method basically iterates through all of the fields submitted in the form and stores them in the corresponding field of a newly created SPListItem. Nothing really special, but it shows how the values get collected from the user and stored in the wiki item. One important part to note is the call to list.RootFolder.Files.Add. This creates a new file (and the associated SPListItem) in the list. The interesting bit is the second parameter: SPTemplateFileType.WikiPage. If you follow the Add method and look at what it does, you'll see that the second parameter sets a path variable to "DocumentTemplates\wkpstd.aspx". The Add method then creates and returns (as the new file in the wiki library) a ghosted instance of this page. In other words, every wiki page in every wiki on your site uses the file located at ...12\TEMPLATE\DOCUMENTTEMPLATES\wkpstd.aspx as a template. With all of the standard caveats about editing the default SharePoint files, if you change that one file, it will reflect in every wiki page that has not been unghosted. Not too useful, perhaps, but interesting.

A few final things to note about CreateWebPage.aspx:

  1. It displays the fields from the current list via a ListFieldIterator. This means that if you add columns to your wiki list, they WILL show up on this page. Likely, that's what you wanted, but perhaps not. Anyway, it's what happens.
  2. It always creates the SPListItem in the root of the list. Folders are not supported.

At this point, we have a new file in the wiki library named with the Name supplied by the user, with basic contents drawn from wkpstd.aspx and specific contents from whatever was entered by the user in the rich text control displayed for the Wiki Content field.

Converting Wiki Markup

So far, so good, except that the text has not been converted from wiki markup to HTML. It still contains a bunch of [[page]] entries. When do they get converted to links: or ?

This is the point where wikis start to take on a little bit of a split personality. There are two conditions in which wikis can be displayed:

  1. Normal Display - all wiki markup/links are converted to properly styled HTML and displayed to an enduser: or
  2. Editing Display - all wiki markup/links is shown unconverted as regular textual markup: [[my page]] or [[a new page]].

Looking at these one at a time...

Normal Display

When the wiki page is displayed for end users, the value of the WikiContent field (the name of the field in the wiki List) is displayed via: . Inside the wiki List, the field is a simple Multiline Text field with RichText = true

Note that the FieldName attribute references "WikiField". In the UI, the field displays the name "Wiki Content" but the internal name is "WikiField" and that's what we need to reference it by. You can see this if you mouse-over the field in the List Settings page and take a look at the status bar:

Because the field in the List is simply a MultiLine Text field, the FormField tag above causes it to display as a Rich Text field in the UI.

We're now finally at the point where the wiki markup gets translated to HTML. It happens in the RenderFieldForDisplay method of the Microsoft.SharePoint.WebControls.RichTextField class. The method itself is pretty short, the meat of it for wikis being the following three lines:

output.Write("<div class=\"ms-wikicontent\">");

output.Write(field.GetFieldValueAsHtml(this.ItemFieldValue, base.ListItem));

output.Write("<p></p></div>");

The call to GetFieldValueAsHtml will eventually make a call to Microsoft.SharePoint.SPFieldMultiLineText.ProcessWikiLinks (assuming that WikiLinking is set to true for the RichTextField, which it is in our case). ProcessWikiLinks iterates through the ForwardLinks collection of the SPListItem that is our wiki page and converts the wiki links [[my page]] to this HTML if the target page already exists:

<a id="ctl00_PlaceHolderLeftActions_RecentChanges_ctl00_RecentChangesIterator_ctl01_RecentChange" class="ms-navitem" href="/wsswiki/Wiki%20Pages/my%20page.aspx">my page</a>

And this HTML if it does not:

<div><a class=ms-missinglink href="http://moss1/wsswiki/_layouts/CreateWebPage.aspx?List={0971da62%2Da81d%2D4879%2Da11b%2D17bb362f76bb}&RootFolderUrl=%2Fwsswiki%2FWiki%20Pages&Name=a%20new%20page" title="a new page - click to define topic">a new page</a></div></div><p></p></div>

The latter snippet, as you can see, decorates the anchor tag with a class attribute that is responsible for the on-screen display that indicates the wiki page does not yet exist. Notice also the href attribute is a call to CreateWebPage.aspx. This starts the process for a new page, which we covered at the top of this article.

Notice that the text is converted for every page load. It is not converted and stored. For wiki links, this is preferred as pages can be added or deleted at any time, so we need to process the links based upon the current status of the pages. In the CKS Enhanced Wiki, we add a lot more markup parsing which would be expensive to do for every page load, so we handle that slightly differently. We'll cover that in another post.

Editing Display

After all of the above, the editing display is easy. As we now know, our Wiki Content (WikiField) is just a multi-line text field in SharePoint. In the edit view, the standard EditForm.aspx page simply renders this as a regular RichTextField with the Rich Text Editor toolbar in place. Nothing happens to the text because it is stored in its raw form - i.e. with the wiki markup intact. We don't need to convert the text, as mentioned above, because it is stored in the unconverted state. So, displaying the wiki field for editing is no different thatn showing any other SharePoint field for editing.

There is one minor element that does get added specifically for wiki fields: the little instructions blurb that shows up below the field:

This is handled in the Microsoft.SharePoint.WebControls.RichTextField.RenderFieldForInput method. After all of the other processing has happened, the following lines add this text:

if (field.WikiLinking)

{

output.Write("<span class=ms-formdescription>");

output.Write(SPResource.GetString("WikiLinkInstructions", new object[0]));

output.Write("</span>");

}

As you can see, the text itself is taken from a resource file. The RenderFieldForInput method also handles adding the proper JavaScript to the page to handle the RichText editor functionality by making a call to the SPUtility.TextAreaToRichTextClientScript method.

That's it for editing.

One Other Point

One more thing that it is important to know is that the default wiki list limits the number of RichText fields it can contain to just one. This means that it is not possible to have >1 wiki field on an item.

Conclusion

So there you have it, a little waltz through the default SharePoint wiki to show how it handles converting wiki markup to HTML and the creation of pages. All in all, it's pretty basic stuff. In a later post, I'll cover how we change this process to overcome some internal designations and also add support for a lot more wiki markup and some other functionality.

Dave

1 - 10 Next

 ‭(Hidden)‬ Admin Links

SharePoint Server MVP Community Kit for SharePoint Philly Office Geeks ISPA