Feb 21

Changing Application Pool for existing Web Applications

There are many articles about SharePoint 2010 and application pool management. One of those topics is the number of application pools to be used by your SharePoint web applications.  I like this post by Todd Klindt: Top 10 SharePoint 2010 Configuration Mistakes. Mistake #5 is about application pools and web applications. There’s no need to have an application pool for each web application. Sharing an application pool by several web applications saves you lots of megabytes of memory.

With Central Administration it is undoable to set a different application pool for a web application. First, you have to delete the web application without deleting the IIS site and content databases. Then you can recreate the web application using the correct application pool, the existing IIS site and finally attaching the content databases. If you have features activated at the web application scope, you also have to reactivate them again and Lord knows what happens then!

You don’t want this. No, you don’t.

Alternative? PowerShell of course! Basically you will need a reference to the application pool first, then a reference to the web application and then you can update the ApplicationPool property.

At one of my customer’s SharePoint Farm I noticed that every web application had it’s own Application Pool. What a waste of resources that is. Time for scripting, I guess.

Here’s my PowerShell Script:

$config = Get-Content Set-DefaultApplicationPoolConfig.xml
$defaultAppPoolName = $config.WebApplications.defaultAppPoolName

[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Administration") 

$service = [Microsoft.SharePoint.Administration.SPWebService]::ContentService 

Write-Host "Checking presence default application pool $defaultAppPoolName..." -NoNewline

[Microsoft.SharePoint.Administration.SPApplicationPool] $newAppPool = $service.ApplicationPools[$defaultAppPoolName] 

## The Default Application Pool MUST exist!
if($newAppPool -eq $NULL)
{
    Write-Host ""
    Write-Host -ForegroundColor Red "The default application pool '$defaultAppPoolName' does not exist."
    Write-Host -ForegroundColor Red "Please ensure the application pool '$defaultAppPoolName' is already registered in SharePoint."
    exit
}
else
{
    Write-Host -ForegroundColor Green "[OK]"
}

foreach( $webAppConfig in $config.WebApplications.WebApplication )
{
    $url = $webAppConfig.url
    Write-Host "Updating $url..." -NoNewline 

    $webApp = Get-SPWebApplication $url -ErrorAction SilentlyContinue

    if( $webApp -eq $null )
    {
        Write-Host -ForegroundColor Yellow " [Web application does not exist]"
        continue
    }

    $currentAppPool = $webApp.ApplicationPool
    if($currentAppPool.Name -eq $defaultAppPoolName)
    {
        Write-Host -ForegroundColor Green " [Change not needed]"
    }
    else
    {
        $webApp.ApplicationPool = $newAppPool
        $webApp.Update()
        $webApp.ProvisionGlobally()
        Write-Host -ForegroundColor Green " [Done]"
    }
}

As you can see this script uses a XML configuration file. In this file I configure what the default application pool name is and which web applications I want to change to this application pool.

XML Configuration file:

<WebApplications defaultAppPoolName="SharePoint - Default Web Application Pool" >
    <WebApplication url="http://dummy.ochadev.local" />
    <WebApplication url="http://redactie.diwug.local" />
    <WebApplication url="http://playground.ochadev.local" />
    <WebApplication url="http://testdummy" />
</WebApplications>

And this is the result:

Set-DefaultApplicationPool

After executing the script the SharePoint web applications are registered with the new default application pool. This also means that whenever you extend your farm with a new SharePoint server, it also has the correct web applications and their application pools.

Good luck and enjoy.

Download script

Jan 27

Deploying Solutions with Language Packs with PowerShell

In previous post(s) I described deploying your solutions with PowerShell. Lately I encountered the situation I had to deploy a solution language pack. This is a regular WSP file with the same Solution ID as the original solution, but containing localization resources for specific languages. The purpose of solution language packs is to add localization for additional languages after the original solution package has already been distributed without having to upgrade the original solution package. I suggest you read this MSDN article about Working with Language Pack Farm Solutions.

Deployment

For deploying these solution language packs the same PowerShell cmdlets can be used: Add-SPSolution and Install-SPSolution. However, this time the –Language parameter must be used. Keep in mind that the original solution is deployed first.

Removal

For removing these solution language packs the same PowerShell cmdlets can be used: Remove-SPSolution and Uninstall-SPSolution. Again, the –Language parameter must be used. Also, the original solution is still present in the farm.

Extending the script

So, how can we fit this deployment and removal in our existing script Solutions.ps1?

First of all, we need to tell our script we’re dealing with a solution language pack. I have created a Solution.xml file with solutions to deploy, remove or update (I kept it simple):

<Solutions>
  <Solution file="..\SharePointLearningKit.wsp" deploy="TRUE" remove="TRUE" update="FALSE" />
  <Solution file="..\SharePointLearningKit-1043.wsp" deploy="TRUE" remove="TRUE" update="FALSE" lcid="1043" />
</Solutions>

The lcid attribute tells the script that it is a solution language pack and which language it is. Another important thing is that I have named the wsp file correctly: the same name as the original code solution and a suffix “-{lcid}”. I will use this in the scripts.

Secondly, our deploy.ps1 (and also remove.ps1 and update.ps1) needs to be changed in order to use the Solutions.xml file.

$deployConfig = Get-Content Solutions.xml

foreach( $solution in $deployConfig.Solutions.Solution )
{
    if( $solution.deploy.ToUpper().Equals("TRUE") )
    {
        if( $solution.lcid )
        {
            .\solution.ps1 -solution $solution.file -deploy -url $url -language $solution.lcid
        }
        else
        {
            .\solution.ps1 -solution $solution.file -deploy -url $url
        }
    }

}

When the solution configuration item contains a lcid attribute it calls the solution.ps1 script with the language parameter provided with the lcid value.

Third and final step is to change the solution.ps1 script to actually deploy (or remove or update) the solution language pack. As just mentioned the solution.ps1 script has an additional parameter Language:

param(
    [string]$solution,
    [string]$url,
    [switch]$deploy = $false,
    [switch]$remove = $false,
    [switch]$update = $false,
    [int]$language
)

Then if $language is provided (the actual value will be greater then 0), we are dealing with a solution language pack and we need to know the name of the original solution.

# locate the solution file:
$solutionFile = Get-ChildItem $solution
# get the solution Name
$solutionName = [System.IO.Path]::GetFilename($solutionFile)

if( $language)
{
    #solutionCoreName is the name of the original solution package
    $solutionCoreName = $solutionName.Replace( "-$language", "" )
}

For deployment, the script looks like this:

if($deploy){

    Write-Host ""
    Write-Host -ForegroundColor Yellow "Installing " -NoNewline
    Write-Host -ForegroundColor Green $solutionName -NoNewline
    if( $language )
    {
        Write-Host -ForegroundColor Green "($language)"
    }
    else
    {
        Write-Host ""
    }

    if( $language )
    {
        $sol = Get-SPSolution $solutionCoreName -ErrorAction SilentlyContinue
        $sol = $sol.LanguagePacks | where-object { $_.LocaleId -eq $language }
    }
    else
    {
        $sol = Get-SPSolution $solutionName -ErrorAction SilentlyContinue
    }

    if ($sol)
    {
        Write-Host -ForegroundColor yellow "$solutionName already installed in this farm"
    }
    else
    {
        Write-Host -ForegroundColor yellow "Adding to the farm" -NoNewline
        if( $language )
        {
            Write-Host -ForegroundColor yellow " with langauge $language" -NoNewline
            $sol = Add-SPSolution $solutionFile -Language $language
        }
        else
        {
            $sol = Add-SPSolution $solutionFile
        }
        Write-Host -ForegroundColor Green " DONE"
    }

    if( $language )
    {
        $sol = $sol.LanguagePacks | where-object { $_.LocaleId -eq $language }

        if ( $sol.ContainsWebApplicationResource ) {
            Write-Host -ForegroundColor yellow "Deploying to $url" -NoNewline
            Install-SPSolution -Identity $solutionCoreName -GacDeployment -CasPolicies -Language $language -Webapplication $url
        }
        else {
            Write-Host -ForegroundColor yellow "Deploying" -NoNewline
            Install-SPSolution -Identity $solutionCoreName -GacDeployment -CasPolicies -Language $language
        }
    }
    else
    {
        if ( $sol.ContainsWebApplicationResource ) {
            Write-Host -ForegroundColor yellow "Deploying to $url" -NoNewline
            Install-SPSolution -Identity $solutionName -GacDeployment -CasPolicies -Webapplication $url
        }
        else {
            Write-Host -ForegroundColor yellow "Deploying" -NoNewline
            Install-SPSolution -Identity $solutionName -GacDeployment -CasPolicies
        }
    }

    if( $language)
    {
        $sol = Get-SPSolution $solutionCoreName
        $sol = $sol.LanguagePacks | where-object { $_.LocaleId -eq $language }
    }
    else
    {
        $sol = Get-SPSolution $solutionName
    }

    if ($sol.Deployed -eq $false ) {
        $counter = 1
        while( ($sol.JobExists -eq $true ) -and ( $counter -lt $safeguard  ) ) {
            Write-Host -ForegroundColor yellow "." -NoNewline
            sleep $sleeptime
            $counter++
        }
    }
    Write-Host -ForegroundColor Green " DONE"

}

The interesting part is to get a reference to the solution language pack. First, get the original solution. It has a LanguagePacks property that returns the list of language packages associated with this solution. I want to have a reference to my language pack:

$sol = Get-SPSolution $solutionCoreName -ErrorAction SilentlyContinue
$sol = $sol.LanguagePacks | where-object { $_.LocaleId -eq $language }

If $sol is not null, I know the solution language pack is present in the farm. If it is null then I can add it and deploy it.

Similar lines of code are for the remove and update scenario’s. I have all script files zipped and it can be downloaded here.

Dec 02

QueryFeatures issue when performing a feature upgrade

Awhile ago I had to upgrade a SharePoint solution for a customer. Everything was tested thoroughly and it all worked on my machine. Full of confidence I went to the customer and we executed my PowerShell script to update the solution. And guess what? An error occurred. In this article I want to show you (relevant part of) my PowerShell script how I did the upgrade. what error occurred, why and how I solved it.

The Script

When updating a solution there is no need to retract and re-install the solution and in stead you should use the Update-SPSolution cmdlet. This way feature upgrade actions and/or events are activated. If you have new features, you will have to manually install them.

param( [string]$url="http://intranet.ochadev.local" )

[/xml]$deployConfig = Get-Content Solutions.xml

foreach( $solution in $deployConfig.Solutions.Solution )
{
    if( $solution.deploy.ToUpper().Equals("TRUE") )
    {
        .\solution.ps1 -solution $solution.file -deploy -url $url
    }

    if( $solution.update.ToUpper().Equals("TRUE") )
    {
        .\solution.ps1 -solution $solution.file -update -url $url
    }

}

Write-Host -ForegroundColor Yellow "Restarting the SharePoint Timer Service..." -NoNewline
Restart-Service SPTimerV4
sleep -Seconds 8
Write-Host -ForegroundColor Green " DONE"

# Install new features
Install-SPFeature -Path Customer.SharePoint.Intranet.Feature1
Install-SPFeature -Path Customer.SharePoint.Intranet.Feature2

# Enable feature 1
Write-Host -ForegroundColor Yellow "Activating feature 1" -NoNewline
Enable-SPFeature -id f2d467b7-bbf3-4c56-8b99-919ea06121c0 -url $url -Confirm:$false
Write-Host -ForegroundColor Green " DONE"

# Enable feature 2
Write-Host -ForegroundColor Yellow "Activating feature 2" -NoNewline
Enable-SPFeature -id da0a0018-2a8a-43be-9c10-fa47317939b1 -url $url -Confirm:$false
Write-Host -ForegroundColor Green " DONE"

# Update current features
$site = Get-SPSite $url

$featuresToUpgrade = $site.QueryFeatures("Site", $true)
foreach ($f in $featuresToUpgrade)
{
    Write-Host -ForegroundColor Yellow "Upgrading feature " $f.Definition.DisplayName -NoNewline
    $f.Upgrade( $false )
    Write-Host -ForegroundColor Green " DONE"

}

$featuresToUpgrade = $site.QueryFeatures("Web", $true)
foreach ($f in $featuresToUpgrade)
{
    Write-Host -ForegroundColor Yellow "Upgrading feature " $f.Definition.DisplayName -NoNewline
    $f.Upgrade( $false )
    Write-Host -ForegroundColor Green " DONE"

}

Write-Host -ForegroundColor Green "Finished upgrading"

The script starts with deploy new solutions and updating existing ones. This list of solutions is stored in a XML configuration file. After that the script installs and activates new features. And last but not least it updates any feature that needs to be updated. To know what features need to be updated, the method QueryFeatures is used.

The Error

When the line

$featuresToUpgrade = $site.QueryFeatures("Web", $true)

is executed the following error occurred:

An error occurred while enumerating through a collection: Unable to access web scoped feature (Id: 00bfea71-d1ce-42de-9c63-a44004ce0104) because it references   a non-existent or broken web (Id: e8a747cc-2cf4-49fe-8b20-ea44d6fae62b) on site ‘http://intranet.ochadev.local’.

Exception: System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0×80070005 (E_ACCESSDENIED))

   at Microsoft.SharePoint.Library.SPRequest.GetAllWebsOfSite(String bstrUrl, Object& pvarWebs, Object& pvarWebIds, Object& pvarParentWebs, Object& pvarLangs, Object& pvarTitles, Object& pvarUIVersions, Object& pvarFlags, Object& pvarWebTemplates, Object& pvarConfigurations, Object& pvarMasterUrls, Object& pvarCustomMasterUrls)

   at Microsoft.SharePoint.SPSite.SPWebCollectionProvider.GetWebsData(String[]& strNames, String[]& strServiceRelUrls, Guid[]& guidWebIds, Int32[]& nLanguages, String[]& strTitles, String[]& strDescriptions, String[]& strCreationTimes, String[]& strModifiedTimes, Boolean[]& bUserIsWebAdmins, Int32[]& nWebTemplates, Int16[]& nProvisionConfigs, Int16[]& nMeetingCounts, Int32[]& nUIVersions, Int32[]& nFlags, String[]& strMasterUrls, String[]& strCustomMasterUrls)

   at Microsoft.SharePoint.SPWebCollection.EnsureWebsData()

   at Microsoft.SharePoint.SPWebCollection.get_Item(Guid id)

   at Microsoft.SharePoint.SPFeatureEnumeratorBase.GetCachedWeb(SPSite site, Guid webId, Guid featureId).

Hmm. Did I missed something? I was logged on the server as a farm administrator (it was DOMAIN\Administrator. Not really a best practice, but sadly that was the case), started PowerShell as an Administrator. All okay, don’t you think?

The Solution

Access denied. It got me thinking. Why do I have an Access Denied exception? I am a farm administrator. But, that does mean I have explicit access to a site or web. Was this user account somehow logged in the site before? Let’s take a look. And yes, there it was:

image

The (Farm) Administrator account had logged in the site some day and then it got very little permissions set explicitly. I removed this permission for the Administrator account and tried my script again. It worked like a charm now!

Nov 07

Creating Additional SharePoint 2010 Farm Administrators

Lately, I have noticed that people are struggling with user accounts that need to be a SharePoint 2010 Farm Administrator. When you install SharePoint 2010 the active account will become a Farm Administrator (since it’s a member of Local Administrators in order to install software on the server) as well as the account you have set up for the SharePoint configuration database access. But it is very reasonable that you want other accounts to manage the farm daily. Accounts that are traceable to a real world person. In this blog post I want to sum up the actions you will need to take in order to create an additional full-blown Farm Administrator.

First step is to log into Central Administration with your current Farm Administrator account. Then open the link Manage the farm administrators group.

image

Click the New button to add your user account.

image

Enter the user name or select the user account you want to promote to Farm Administrator. Click OK.

Now, at this stage the user account can manage the farm by using Central Administration. If you

Second step is to logon the SharePoint server with the Farm Administrator account that you have used to install SharePoint or a Domain Administrator.

Then run the SharePoint 2010 Management Shell as Administrator. Now, you’ll need to give the user account PowerShell access to the SharePoint databases. So, when the account needs to manage solutions you will have to give PowerShell permissions to the SharePoint Configuration database.

Add-SPShellAdmin –username <youraccount>

Example:

image

This PowerShell cmdlet performs a couple of things. First, it adds the user account to the WSS_Admin_WPG group on ALL front-end webservers. This groups allows you to access SharePoint resources such as Logs and WebServices.

image

Then it also adds the user account to the SharePoint_Shell_Access role on the SharePoint database. If that database does not have this role yet, it will be created as well.

image

Back to the Add-SPShellAdmin cmdlet. If you do not specify a database, the SharePoint Configuration database is used.

If you have existing content database in the farm, you will have to use this cmdlet for every content database you want to manage by PowerShell.

Add-SPShellAdmin –username <youraccount> –database <contentdb>

Example:

image

All necessary steps are taken and you can test your account if it can perform all farm administrative tasks, both in Central Administration as well as with PowerShell.

Summary

Creating additional SharePoint Farm Administrators is not that hard to do. After adding the user to the Farm Administrators group in Central Administration, you should also use the PowerShell cmdlet Add-SPShellAdmin to give the user PowerShell access to the Farm and content databases.

More information about the cmdlet Add-SPShellAdmin: http://technet.microsoft.com/en-us/library/ff607596.aspx

Oct 04

SharePoint Conference 2011 – Day 2

Second day. Gonna be a long day with great sessions. This morning I already attended 2 sessions: Hit The Ground Running Claims Authentication with SharePoint 2010 by Steve Peschka and after that Best Practices Around SharePoint 2010 User Profiles by Scott Jamison.

The Claims session was tough. I was out of my comfort zone, so I could hardly follow Steve. Yeah, I know the concept/basics of claims, I know what ADFS stands for, but I never set it up myself. Yet. He had a lot of slides about what he was going to do and little demo’s. Too bad. Think I would hit the ground earlier if more stuff was demoed. Good to know, that the People Picker resolves all input. So if you have 4 claim types, then the people picker will come up with 4 results. The workaround for this is to write your own claims provider.

The second session was about best practices around user profiles. I did this many times and I can allmost dream the actions to take. Scott is an excellent speaker and the session was awesome. The content was familiar to me, but I didn’t care. Scott dared the audience to demo User Profile Synchronization. He randomly picked one person…. Spencer Harbar! You know now, that the demo couldn’t go wrong.

Spencer mention the “stuck on starting” issue and named 3 reasons why:

  • Farm Administrator is not Local Administrator prior to provisioning
  • Farm Adminitrator has no Logon Locally rights
  • The password for the Farm Administrator given when starting the UP Sync Service is wrong. Although it verifies if both password entered are the same, but the credentials are NOT validated.

Just had a great lunch and have met new people: Bas Lijten (Achmea) and Marc D. Anderson (Developer of the jQuery Library for SharePoint Web Services (SPServices))

So, my next session about HTML5 and jQuery is about to start. Laterzzz

Oct 04

SharePoint Conference 2011 – Day 1 (continued)

What a first day! After my post yesterday I have attended Planning and Managing Sandbox Solutions and Services by Maurice Prather. Man, what a deep dive session it was. I had troubles making notes and at the same understand what he was saying. I needed to pay attention really closely. One thing is clear, the default settings are killing you. The Sandbox Code Service is by default configured to 1 worker process and 1 connection per process. This means only 1 user at a time can use the sandbox solution. Shocking!

image

For more detailed info about this session, read this blog post by Ernst Wolthaus

http://itblog.wolthaus.net/2011/10/spc11-planning-and-managing-sandboxed-solutions-spc376/

I really enjoyed this session and the info was really good.

The last session I attended was a development session, level 400. At last! Smile It was all about Developing and Extending ECM feaures by Paul Swider. His agenda showed several subjects that comes with ECM, such as libraries and lists, Document Management, Taxonomy Object Model and Workflow Object Model. Cool.

Then he started with Document Management and especially the Document ID and how to extend it. He started with a OOB Document Center site and saved it as a template. He explained that this would result in a WSP and that you can import it in Visual Studio 2010 and that’s your start having all artifacts, the features you can extend etc… Excuse me? Why would I as a dev go this way? Come on, this it not level 400 worthy. At least, use webtemplates, you know this is the way to go with SP2010. He took almost 90% of the time spending on DocumentIdProvider. Saw some bad code as well. You cannot delete a content type from a list first and then add your custom one. An exception can be expected when you have just one content type. Sigh…

Time to party then. After a drink, we went to Morton’s Steak House. Man, what great steaks they have! If you can, you should go there definitely!

Oct 03

SharePoint Conference 2011 – Day 1

So, it has started. This morning was the keynote and the room was packed! Over 7500 attendees are present at the SPC11. Awesome. And no, there isn’t a word about vNext. It’s all about SharePoint 2010 and how succesful it is. Since there a lot of blog posts already about the keynote I won’t re-write all the details, but I leave you with this post by Thomas Sondergaard

http://sharepointsharpener.wordpress.com/2011/10/03/spc11-keynote-online-focus-delivering-productivity-and-next-year-las-vegas/

After the keynote it was time for coffee (needed it really bad) and the breakout sessions.

Managing LOB data with BCS and Search – Level 300

I had many expectations about this session. I’ve some some BCS solutions and I hoped to learn some more. However, the session was a little bit slow, heard a couple of Oops and in the end it was merely an Overview. Too bad. Didn’t learn a thing actually. One thing was clear: using BCS comes with a lot of security configuration using the Secure Store Service. The presenter Shannon Bray started his session with a couple of slides showing several errors due to security. And during the session he got all these messages while not meant to be…

Have to hurry now. Next session about Managing Sandbox Solutions and Services it about to start. Laterzzzz

Oct 03

MySPC11 – What’s Scheduled for Day One

There are way to many interesting sessions at the SharePoint Conference 2011, so I had to choose, change, shuffle and choose again. For Monday this is my schedule. But it could change at the last moment… Winking smile

 

08:30 Keynote
11:00 Managing LOB data with BCS and SharePoint Search
-or-
Overview Enterprise Search in SharePoint 2010
14:00 Attractive BI : Dashboards, Pivots, Scorecards, KPIs and Reports
-or-
Planning and Managing Sandboxed Solutions and Services
15:45 Best Practices for Building your website for scale with SharePoint 2010
-or-
Developing and Extending Enterprise Content Management features
Evening Party!

 

Just had a great day. Registered for SPC11, then rented a van and drove together with Microsoft NL and other SharePoint community fellows to Hollywood and Venice Beach. In the evening we had some welcome parties by SPC11 and Microsoft NL.

Time to go to bed now. Must be fresh tomorrow

Oct 02

Arrived at Anaheim, SharePoint Conference 2011

Today I arrived in Anaheim where the Microsoft SharePoint Conference 2011 is taking place. Along with my Mavention colleagues Waldek Mastykarz, Robert Jaakke and Lennard van Leuven, we will attend lots of sessions and meet lots of SharePoint people. I am looking forward to it!

sharepoint_conference2011

Just stay tuned and watch my blog as I will post many articles about what I’ve seen and heard during the days. I am staying at the Hilton Hotel so if you like to meet and greet, just give me tweet or else you’ll probably find me at one of the many SharePints!

Enjoy the conference!

Sep 04

Customizing ListItem Forms Using RenderingTemplates

I guess, you all know the Tags and Notes functionality of SharePoint 2010 by now. On the My Profile page you can see all your social comments (the tags and notes) you have given on the pages as well as the possibility to leave a note. This Note Board is actually a webpart (SocialCommentWebPart).

SocialCommentsWebPart1

I was asked to add this webpart on the display form for a particular list, so users can comment on a list item. If you are not a developer you would probably start SharePoint Designer right now, create a custom Display Form and add the webpart to this form and voilá. But if you are a developer you want this solution to be deployable and maybe generic. So no SharePoint Designer (which, by the way, replaces the original code with it’s own DataViewWebPart and XSLT.)

RenderingTemplates

List forms are using RenderingTemplates. These templates define how controls are rendered. For instance, choice fields can be rendered as a dropdown list or radiobuttons. These templates can be found in the CONTROLTEMPLATES folder in the SharePoint root folder. Opening this folder will show you many ASCX files.

SocialCommentsWebPart2

The one we need is the DefaultTemplates.ascx. This file contains many templates, such as CompositeField, BlogForm, CreatedModifiedInfo and ListForm. The last one mentioned is the template we need to customize and add the SocialCommentsWebPart.

In my Visual Studio 2010 SharePoint project I have added a SharePoint Mapped Folder item pointing to the CONTROLTEMPLATES folder. Then I created a new UserControl and deleted all the CodeBehind files. Then I modified the ASCX so no reference is present to the deleted CodeBehind:

<%@ Control Language="C#"   AutoEventWireup="false" %>

From the original DefaultTemplates.ascx I copied the ListForm template and renamed it to CustomListForm:

<SharePoint:RenderingTemplate id="CustomListForm" runat="server">
    <Template>
        <span id='part1'>
            <SharePoint:InformationBar runat="server"/>
            <div id="listFormToolBarTop">
            <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator="&amp;#160;" runat="server">
                    <Template_RightButtons>
                        <SharePoint:NextPageButton runat="server"/>
                        <SharePoint:SaveButton runat="server"/>
                        <SharePoint:GoBackButton runat="server"/>
                    </Template_RightButtons>
            </wssuc:ToolBar>
            </div>
            <SharePoint:FormToolBar runat="server" />
            <SharePoint:ItemValidationFailedMessage runat="server" />
            <table class="ms-formtable" style="margin-top: 8px;" border="0" cellpadding="0" cellspacing="0" width="100%">
            <SharePoint:ChangeContentType runat="server"/>
            <SharePoint:FolderFormFields runat="server"/>
            <SharePoint:ListFieldIterator TemplateName="ListFieldIterator" runat="server"/>
            <SharePoint:ApprovalStatus runat="server"/>
            <SharePoint:FormComponent TemplateName="AttachmentRows" runat="server"/>
            </table>
            <table cellpadding="0" cellspacing="0" width="100%"><tr><td class="ms-formline"><img src="/_layouts/images/blank.gif" width='1' height='1' alt="" /></td></tr></table>
            <table cellpadding="0" cellspacing="0" width="100%" style="padding-top: 7px"><tr><td width="100%">
            <SharePoint:ItemHiddenVersion runat="server"/>
            <SharePoint:ParentInformationField runat="server"/>
            <SharePoint:InitContentType runat="server"/>
            <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&amp;#160;" runat="server">
                    <Template_Buttons>
                        <SharePoint:CreatedModifiedInfo runat="server"/>
                    </Template_Buttons>
                    <Template_RightButtons>
                        <SharePoint:SaveButton runat="server"/>
                        <SharePoint:GoBackButton runat="server"/>
                    </Template_RightButtons>
            </wssuc:ToolBar>
            </td></tr>
            <tr><td width="100%" style="height:300px"><PortalWebControls:SocialCommentWebPart runat="server" /></td></tr>
	    </table>
        </span>
        <SharePoint:AttachmentUpload runat="server"/>
    </Template>
</SharePoint:RenderingTemplate>

Just before the closing table tag, I added an extra table row and cell to add my SocialCommentWebPart. Since this webpart is in another assembly you need to register it. I used PortalWebControls as the tagPrefix:

<%@ Register TagPrefix="PortalWebControls" Assembly="Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.Portal.WebControls"%>

(Note: in order to use this SocialCommentWebPart successfully, you’ll have to set up User Profiles for your webapplication)

It is important to deploy your ASCX file in the CONTROLTEMPLATES folder! Do not create a subfolder, it won’t work.

Configure the list definition

Next step is to configure the list to use this custom template. In my solution I have a list definition. In the schema.xml you have the <Forms> tag. Here are the forms defined for displaying, adding and editing items. There is a Template attribute to tell SharePoint which RenderingTemplate to use with that form.

<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint" Title="Berichten" FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/Berichten" BaseType="0" xmlns="http://schemas.microsoft.com/sharepoint/">
  <MetaData>
    <ContentTypes>
    </ContentTypes>
    <Fields>
    </Fields>
    <Views>
    </Views>
    <Forms>
      <Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" Template="CustomListForm" />
      <Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
      <Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
    </Forms>
  </MetaData>
</List>

In my example I have the template CustomListForm set for the DisplayForm. By default, the template ListForm will be used for all list items.

Basically, this is it. However, there is one thing… You probably use content types. I do. That’s why it didn’t work out for me in the first place. So, if you do use content types, and you have a list definition based on a content type, you also have to set your custom Rendering Template for your content type:

<ContentType ID="0x01009A851469C3B5154C843876238F54D125" Name="Bericht" Group="Custom" Inherits="FALSE" Hidden="false" ReadOnly="false" Sealed="false">
  <FieldRefs>
    <FieldRef ID="fa564e0f-0c70-4ab9-b863-0177e6ddd247" Name="Title" DisplayName="Titel" />
    <FieldRef ID="d44cb4d4-a259-4a96-b859-dbc769dddf55" Name="Omschrijving" DisplayName="Omschrijving" />
  </FieldRefs>
  <XmlDocuments xmlns="http://schemas.microsoft.com/sharepoint/">
    <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
      <FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
        <Display>CustomListForm</Display>
        <Edit>ListForm</Edit>
        <New>ListForm</New>
      </FormTemplates>
    </XmlDocument>
  </XmlDocuments>
</ContentType>

In the <FormTemplates> element, you have the three forms: <Display>, <Edit> and <New>. Set your custom rendering template and you’re good to go. Remember, this content type XML definition exists in both the element.xml of your Content Type as well as in the Schema.xml of your list definition!

So, how does it look like now? Here’s a screenshot of my Social Comments Webpart in the display form of a list item:

SocialCommentsWebPart3

Override OOB templates?

Now, SharePoint offers a lot of rendering templates OOB. Is it possible to override, for example, the default template ListForm? The answer is… Yes! You can. But there’s a catch.

Now, every 2 weeks we Mavens get together to share our knowledge and experience. I was demonstrating this topic about Rendering Templates and my colleague Waldek Mastykarz also used this technique in one of our products (Mavention Anonymous Rating). During this night, it came clear you can override OOB templates simply by using the same name in your ControlTemplate file. So, if you use ListForm for your custom template, then every list would use your customized template. How cool is that? But what is the catch? That night we asked ourselves: what if another 3rd party solution also uses a custom ListForm template? Then you have 3 templates with the same name ListForm. What happens then? So, Waldek said his famous words: “One way to find out…” And he did. It turns out that SharePoint alphabetically sorts all the ControlTemplate files and the first one on that sorted list is the big winner! Hmmm… Conclusion: it is not 100% guaranteed that your overridden custom ListForm will be used. I guess you will need to be smart on how to name your ASCX file.

More information on Rendering Templates can be read here:

http://msdn.microsoft.com/en-us/library/aa544142.aspx

Older posts «