|
|
-
When developing JavaScript solutions, I like to provide a tracing mechanism that can be turned on and off to aid debugging. The IE Developer Tools, debugger and Fiddler are very robust debugging tools, but they don’t enable client script code to trace custom messages to a “log” that can be reviewed by developers. The SharePoint 2010 Developer Dashboard is designed for server-side tracing, but it can also be used for client-side trace messages. The approach is simple: tracing code in your client script appends html to the Developer Dashboard when the Dashboard is visible. Here is the JavaScript (note this version depends on jQuery): function DeveloperDashboardSection(sectionId, sectionTitle) { this.Title = sectionTitle; this.Id = sectionId; } DeveloperDashboardSection.prototype = { Id: '', Title: '', DeveloperDashboardVisible: function() { return$('DeveloperDashboard').length; }, EnsureSection: function() { var section = $('#'+ this.Id); if(!section.length) { var sectionHtml = "<table id='"+ this.Id + "' width='49%'><tbody><tr><th colspan='2'>"+ this.Title + "</th></tr></tbody></table>"; $('#CurrentScopeValues').append(sectionHtml); } }, AddEntry: function(context, message) { if(!this.DeveloperDashboardVisible) return; this.EnsureSection(); var entryHtml = "<tr><td nowrap='nowrap'><span>"+ context + "</span></td><td nowrap='nowrap'><span>"+ message + "</span></td></tr>"; $('#'+ this.Id).append(entryHtml); } };
To use the script, create an instance of the DeveloperDashboardSection and use the instance throughout your code to trace custom messages to the Dashboard: var theSection = new DeveloperDashboardSection('sampleId', 'Custom Section'); theSection.AddEntry('someMethod()', 'The method was called'); The messages will appear on the Developer Dashboard in it’s own section: When the Dashboard is not visible, the tracing calls are benign.
|
-
Call me picky, but I think it’s important to consistently name your SharePoint databases—especially on a shared SQL Server hosting multiple farms and applications. Not only is it a good practice, but by showing that you care about the databases, you’ll earn the respect of even your most grumpiest DBAs. I wish the SharePoint administrative interfaces ushered you into a naming convention, but the reality is that consistent naming requires diligence and a little planning. I would bet that a majority of the SharePoint installations in the wild were configured using using the Products and Technology Wizard GUI. When you use the GUI, it creates the Central Administration content database without giving you the opportunity to provide a meaningful name. If you’ve seen a database named SharePoint_AdminContent_[YOURGUIDHERE], you know what I’m talking about. Imagine you are supporting multiple SharePoint farms—is it easy to tell which database belongs to which farm? Shouldn’t it be?
Naming Convention
Here is the naming convention I recommend for SharePoint 2007:
SharePoint_[Farm Name]_[Database Type]_[Name] where database type is one of the following:
- Config
- Content
- WSSSearch
- SSP
- SSPSearch
For example, the AdventureWorks MOSS 2007 Enterprise Intranet farm would have the following databases:
| Database |
Purpose |
| SharePoint_AWIntranet_Config |
Farm Configuration |
| SharePoint_AWIntranet_Content_CA |
Central Administration Content |
| SharePoint_AWIntranet_Content_Intranet |
Company Intranet Content |
| SharePoint_AWIntranet_Content_DefaultSSP |
Default SSP Content |
| SharePoint_AWIntranet_Content_DefaultSSPMySite |
My Site Content for Default SSP |
| SharePoint_AWIntranet_Content_OtherSSP |
Other SSP Content |
| SharePoint_AWIntranet_Content_OtherSSPMySite |
My Site Content for Other SSP |
| SharePoint_AWIntranet_SSP_DefaultSSP |
Default SSP Database |
| SharePoint_AWIntranet_SSP_OtherSSP |
Other SSP Database |
| SharePoint_AWIntranet_SSPSearch_DefaultSSP |
Default SSP Search Database |
| SharePoint_AWIntranet_SSPSearch_OtherSSP |
Other SSP Search Database |
| SharePoint_AWIntranet_WSSSearch_SPWFE01 |
WSS Help Search Web Front End 1 |
| SharePoint_AWIntranet_WSSSearch_SPWFE02 |
WSS Help Search Web Front End 2 |
What I like about this naming convention is that the databases sort nicely. On a shared SQL Server, the SharePoint databases are separated from other application databases. Within SharePoint, each farm’s databases are together. Within each farm, the database types are together, and finally, a meaningful name makes it easy to determine which area it belongs to.
Implementing the Naming Convention
The best way to ensure consistency is to script configuration changes. I use PowerShell to tear down and build up my development environment, and I use the same approach to make changes to production servers. In a future post, I will provide a sample, but essentially, I create a script that takes an environment-specific XML configuration file as a parameter. For example:
Provision-Farm.ps1 johnpowell.development.xml
If you are lucky and brilliant enough to have a naming convention in place before the farm has been configured, it is much easier to implement the database naming convention. Implementing a standard after the fact will require additional steps. So let’s suppose you just installed the SharePoint bits and are ready to configure SharePoint. Stop! Rather than use the GUI, use PSConfig from the command line. This enables you to control the Central Administration content database name. For example, to create the configuration and CA database, use the following command:
psconfig.exe -cmd configdb -create –server sqlcluster.aw.com -database SharePoint_AWIntranet_Config -user svc-sp-aw-farm -password pass@word1 -admincontentdatabase SharePoint_AWIntranet_Content_CA
But what if you have an existing farm? In that case, this post describes how to rename the Central Administration content database and this post outlines the procedure for renaming other content databases.
Conclusion
It is good practice to have a database naming convention, and to define and document it during the planning phase. Although the SharePoint administrative interfaces do not enforce a database naming convention, with a little planning and governance you can implement a standard. Once a standard is defined, scripting configuration changes is the best way to ensure they are implemented consistently. For example, you could have a script Create-WebApplication.ps1 that takes a few parameters and implements your naming convention. SharePoint 2010 has significantly more databases that 2007, making database naming even more important. So here are your “go dos:”
- Define and document a database naming standard
- Create scripts to implement your standards
- Evaluate existing farms against your standards, and create a plan to rename them
|
-
In this walkthrough, I’ll show you how to create a SharePoint 2010 localized feature using Visual Studio 2010. The basic mechanics of localizing a SharePoint 2010 feature are the same as localizing a SharePoint 2007 feature, and is well documented. If you are unfamiliar with localization in SharePoint, please see Additional Resources at the end of this article. In a nutshell, localized resources are stored in Resources (.resx) files, and in SharePoint exist in one of the following locations:
- \14\Template\Features\<Feature Name>\Resources\
- \14\Resources\
- \14\Config\Resources\
- <Virtual Directory>\App_GlobalResources\ (note: resources files from \14\Config\Resources are copied here when provisioned)
Resources files follow a naming convention that consists of a base name such as “HelloLocalization.resx” and culture-specific resources such as “HelloLocalization.es-ES.resx.” In SharePoint, these files must be in a particular location depending on usage. In this walkthrough, I’ll show you how to create a “centralized” resources files that will be stored in 14\Resources. My preference is to store custom files in as few locations as possible within the SharePoint hive. I have also found that certain resources cannot be accessed from certain locations. So with that in mind, let’s get started:
Since we are deploying to 14\Resources, we need to add a mapped folder. Select Add > SharePoint Mapped Folder…
Select the Resources folder:
In the solution, delete the subfolder that was created in the Resources folder. Next, right-click on the mapped folder and select Add New Item. In the Search Installed Templates search box, type Resources to select the Resources file template. Rename the resources file to something unique to your feature:
Set the Build Action to Content for the resources file:
Add your localized strings to the resource file:
For each feature, set the Default Resource File to the resource file name. This enables you to reference a resource without fully qualifying it:
Optional: you can hide a feature when localized resources are not available for it by setting the Require Resources setting to True:
Now you can use the localized strings throughout your feature such as in the feature title and description:
When deployed, the feature appears like this in Site Collection Features:
The resources can also be used in the .webpart file:
<?xml version="1.0" encoding="utf-8"?> <webParts> <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> <metaData> <type name="HelloLocalization.LocalizedWebPart.LocalizedWebPart, $SharePoint.Project.AssemblyFullName$" /> <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage> </metaData> <data> <properties> <property name="Title" type="string">$Resources:HelloLocalization,WebPartTitle</property> <property name="Description" type="string">$Resources:HelloLocalization,WebPartDescription</property> </properties> </data> </webPart> </webParts>
Which appears like this in the Web Part Gallery:
When you add the web part to a page, it appears like this:
The localized resources can also be used in the web part: protected void Page_Load(object sender, EventArgs e)
{
var lang = SPContext.Current.Web != null ? SPContext.Current.Web.Language : 1033;
webPartMessage.Text = SPUtility.GetLocalizedString(
"$Resources:WebPartMessage",
"HelloLocalization",
lang);
}
Which appears like this when added to a page:
To support a different culture, copy the resources file, and rename it to [ResourcesFileName].[Culture].resx. For example, to create a Spanish (Spain) resources file:
Make the culture-specific changes to the resources strings:
If the language pack is installed on the SharePoint server, and the site has been created using that language, the resources file will be used:
For completeness, you should also be aware of the Visual Studio 2010 Feature Resources feature. When you select Add Feature Resource...:
you are prompted for the culture, and the appropriately named resource file is created automatically:
Conclusion
In this walkthrough, I showed you how to localize a SharePoint 2010 feature. Even if you aren’t targeting multiple languages, in my opinion it’s good practice to store resources outside of your compiled code. I hope you found this useful, and your feedback is welcomed!
Additional Resources
|
-
Here is a high-level overview of the steps required to install SharePoint 2010 (Beta 2) on Windows 7 (x64):
- Extract / copy SharePoint setup files to your computer
- Edit files\setup\config.xml and add the AllowWindowsClientInstall = TRUE setting
- Manually install prerequisites
- Enable Windows features (via command line)
- Install SharePoint (standalone), but do not run the configuration wizard
- Install SQL Server 2008 CU 2
- Run SharePoint configuration wizard
Full details can be found on the MSDN article, Setting Up the Development Environment for SharePoint Server.
|
-
The SharePoint 2010 developer dashboard has three display levels (see SPDeveloperDashboardLevel Enumeration.):
- Off (default): The dashboard is not displayed, and there is no UI element to turn it on
- On: The dashboard is displayed, and there is no UI element to turn it off:
- OnDemand: A UI element enables you to turn it on or off
There are a few ways to set the developer dashboard level. One option is to use stsadm –o setproperty –pn developer-dashboard –pv “On”, but in this article, I’ll show you how to control the dashboard through the object model using PowerShell.
To configure the dashboard, use the SPWebService.ContentService object to set properties on the SPDeveloperDashboardSettings class. Here is the PowerShell script to set the level:
param($level = $(throw "level is required. Level is On, Off or OnDemand"))
# import assemblies [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Administration")
# set the level $contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService $contentService.DeveloperDashboardSettings.DisplayLevel = ([Enum]::Parse([Microsoft.SharePoint.Administration.SPDeveloperDashboardLevel], $level)) $contentService.DeveloperDashboardSettings.Update()
Write-Host ("Developer Dashboard Level: " + $contentService.DeveloperDashboardSettings.DisplayLevel)
Here is a complimentary script to view the current settings: # import assemblies
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Administration")
# get the level
$contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
Write-Host $contentService.DeveloperDashboardSettings.DisplayLevel
|
-
One error you may see when packaging solutions with VSEWSS is: Microsoft.SharePoint.Tools.Utilities.VSeWSSServiceException VSeWSS Service Error: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. Like most error messages from VSEWSS, there is not enough information provided to resolve the issue, so I hope to save you some time by providing a workaround that will permanently fix this problem.
When VSEWSS packages your solution, it uses reflection to enumerate the packaged assemblies. It works fine until it encounters an assembly that implements an interface that is defined in another assembly as is often the case in the Microsoft.Practices.* assemblies. I am unsure of exactly why, but the extensions are unable to resolve the dependent assemblies. To work around the issue, you can copy the dependent assemblies to the GAC, but I don’t think this is the optimal solution and doing so will cause pain during development and debugging. Another option is to copy the dependent assemblies to the VSEWSS service bin directory post build. After trying this for awhile, and maintaining the script, I decided it would be much simpler to just copy all assemblies in my solution to the VSEWSS service bin directory on post build. Here is the script: @rem======================================================================
@rem
@rem This script copies assemblies to the VSEWSS bin directory
@rem to eliminate the packaging type load exception that occurs
@rem when an interface is defined in a separate assembly.
@rem
@rem usage call $(ProjectDir)Scripts/CopyAssembliesToVSEWSSBin.bat $(TargetDir)
@rem
@rem======================================================================
@echo off
@echo ========== Locating VSEWSS bin directory ==========
@set vsewssbin=%programfiles%\Microsoft SharePoint Developer Tools 9.0\svc\bin
if not exist "%vsewssbin%" set vsewssbin=%ProgramW6432%\Microsoft SharePoint Developer Tools 9.0\svc\bin
@echo VSEWSS bin: %vsewssbin%
@echo ========== Copying assemblies to VSEWSS bin directory ==========
@xcopy "%1*.dll" "%vsewssbin%" /R /Y
To use the script, create a bat file in your project and call it from a post build event. It does take 64-bit installations into account, and it will also work on your Team Build server.
|
-
In a previous post, I provided a walkthrough for automating builds for VSEWSS 1.3 projects. The script I used was based on the SharePoint Guidance Drop 11, and after using it, I felt it could be simplified. I’m a proponent of implementing the “simplest possible thing that will work,” and the following build script contains the minimal functionality needed to automate the building and packaging of your SharePoint solutions. <?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<ProjectExtensions>
<ProjectFileVersion>2</ProjectFileVersion>
</ProjectExtensions>
<PropertyGroup>
<RunTest>false</RunTest>
<RunCodeAnalysis>Never</RunCodeAnalysis>
<SkipWorkItemCreation>true</SkipWorkItemCreation>
</PropertyGroup>
<PropertyGroup>
<ContosoSolutionDir>c:\NightlyBuild\main\</ContosoSolutionDir>
<ContosoSolutionName>Contoso.sln</ContosoSolutionName>
<ContosoWSPName>Contoso.Deployment.wsp</ContosoWSPName>
<ContosoDeploymentProjDir>c:\NightlyBuild\main\Contoso.Deployment\</ContosoDeploymentProjDir>
<IDEPath>C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\</IDEPath>
</PropertyGroup>
<ItemGroup>
<SolutionToBuild Include="$(BuildProjectFolderPath)/../../main/Contoso.sln">
</SolutionToBuild>
</ItemGroup>
<ItemGroup>
<ConfigurationToBuild Include="Debug|Any CPU">
<FlavorToBuild>Debug</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>
</ItemGroup>
<Target Name="AfterCompile">
<!-- The extensions modify files in the pkg directory, so those files cannot read only-->
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Message="Making the pkg directory editable.">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<Exec Command="attrib -R "$(ContosoDeploymentProjDir)pkg\*.*" /S /D" />
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Id="$(StepId)" Status="Succeeded" />
<!-- Build using /package switch -->
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Message="Packaging wsp.">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<Exec Command=""$(IDEPath)devenv" "$(ContosoSolutionDir)$(ContosoSolutionName)" /Deploy debug /Package" />
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Id="$(StepId)" Status="Succeeded" />
<!-- Copy package to drop folder -->
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Message="Copying wsp to drop folder.">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<Copy SourceFiles="$(ContosoDeploymentProjDir)bin\debug\$(ContosoWSPName)" DestinationFolder="$(DropLocation)\$(BuildNumber)" />
<Copy SourceFiles="$(ContosoDeploymentProjDir)bin\debug\setup.bat" DestinationFolder="$(DropLocation)\$(BuildNumber)" />
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Id="$(StepId)" Status="Succeeded" />
</Target>
</Project>
|
-
A common error you may encounter when using VSEWSS 1.3 is “System.ArgumentException, Value does not fall within the specified range.” This post outlines the common causes and resolutions for the issue.
One suggestion I have seen on the web is to delete the pkg directory since it is auto-generated each time. It is important that you do not do this, because some information about the solution and features is stored in files in the pkg directory. In fact, when doing team development with VSEWSS, the pkg directory must be included in the project and source control so other developers can properly package the solution.
The following outlines the common causes and resolutions:
Remove ASP.NET Web Sites from the Solution
In the VSEWSS 1.3 release notes, there is a known issue: “You may receive the error “Value does not fall within the expected range.” When deploying a SharePoint solution that also contains an ASP.NET Web Project.” The resolution is to “use a separate Visual Studio solution for each of the ASP.NET and SharePoint projects.” If you need a web project, use a web application project instead of a web site.
Ensure Files Added as a Link Were Not Moved, Renamed or Deleted
The most common cause for the ArgumentException is that a file referenced in manifest or rootfiles is missing. One of the things I like to do is develop in a separate projects and link items into the deployment project. This allows me to use a web application project and test changes without having to deploy them to SharePoint first. The downside of linking files is that the links aren’t updated if the source file is moved, renamed or deleted. For example, consider the following project where I have an image file to deploy. Note the shortcut icon indicates the image was added as a link:
If I rename the source file in the Contoso.Web project to “pageerr.gif,” VSEWSS packaging will fail because the file is referenced in the manifest and rootfiles, but cannot be found:
To resolve the issue, look for linked files that have a exclamation icon (which can be difficult to see) and re-link them.
Verify your Installation is not Corrupt
This is the least likely cause, but I experienced this one time. To test your installation, create a new SharePoint project using the Empty template. Package the solution from the solution view. Switch to the wsp view and refresh. If there is something wrong with your installation, you will see a message like this:
To fix the issue, uninstall the extensions, download the latest version of the extensions, and re-install. Re-test creating an empty project.
Conclusion
The Visual Studio Extensions for Windows SharePoint Services can substantially increase your SharePoint development productivity. However, when things go wrong, the extensions can be a productivity killer. The error messages often do not contain enough detail to identify the cause and solution. I hope this post saves you many hours of troubleshooting when you receive an ArgumentException.
|
-
In this walkthrough, I’ll demonstrate how to achieve automated builds and continuous integration by creating a build script for VSEWSS 1.3 solutions. The patterns and practices SharePoint Guidance (11 drop) contains an example build script that this article is based on, but I found some issues with it in practice that I’d like to share. Before we get started, I’d like to talk about the design of the extensions and some of the problems you may encounter with them.
VSEWSS 1.3 stores information used to build the wsp file in a directory named “pkg” that is not included in the project. This works fine for individual development on a single machine, but not so well for team development and build automation. The pkg directory must be manually included in the project so files needed to build the wsp are included in source control. If you forget to do this, your teammates won’t be able to build the wsp, and you risk losing deployment configuration. Furthermore, if you later add a new feature, you also have manually add the new feature subdirectory to the project. In addition to storing configuration information, the pkg directory is also where solution files (feature.xml for example) are automatically generated. When you package the wsp (or refresh in the wsp view), the files in the pkg directory must be checked out to be re-generated. This also causes problems on the build server because the files in the pkg directory are read-only because they are checked into source control. An improved design would be to store wsp configuration information in a folder/file that is part of the project and use the pkg directory only for generating packages based on the wsp configuration files. So now that you are aware of some of the team development “gotchas” with VSEWSS 1.3, I’ll show you, step-by-step, how to configure automated builds.
If you just want to see the build script, get it here. Update: get a simplified build script here.
If you have not installed TFS Build, you will need to install it using the TFS installation media. You will also need to configure a service account such as TFSBuild, and add the account to the TFS server group Team Foundation Licensed Users and the Team Project(s) group Build Services.
To get started, configure the build agent. In the team project select Builds > Manage Build Agents…
Enter the build agent properties for your build server
Create a new build definition by selecting Builds > New Build Definition…
Enter a name for the build definition
Configure the workspace. Note: all files in the source control folder will be downloaded to the local folder you specify, so be sure the location you specify has enough free space
Create the project file (TFSBuild.proj) by selecting the version control folder where you want it stored and selecting Create…
Select the Visual Studio solution to build and click Next
Select the configuration(s) to build and click Next
If you want to run unit tests or code analysis during the build, configure it and click Finish
Now that the project file is created, click OK
Configure how many builds you would like to retain and click OK. Note: you should at least keep the latest of failed or partially succeeded builds or the build log file you need to troubleshoot will be deleted
Enter a share where the build files will be dropped and click OK
Configure what triggers the build, either manually, on a schedule or when files are checked in (continuous integration)
Test the build by highlighting the build definition and selecting Queue New Build…
Click Queue
At this point you should have a working build, but it doesn’t package the wsp file yet

Next, we’ll modify the build script to package the wsp file. The build steps are as follows:
- Clean up any workspaces / files left by the previous build packaging
- Build the Visual Studio solution
- Delete the workspace that was created by the build
- Create a workspace for the wsp packaging build
- Open a second instance of the IDE and package the solution using the /package switch
- Copy the wsp to the drop folder
In addition to this process, a nice to have is to display the packaging steps in the GUI, so we’ll use the BuildStep task to accomplish that
To get started, get the latest version of the Team Build Types folder
Check out the TFSBuild.proj file. Note: when developing the build script, you will have to check it out to work on it, and check it back in to test it
Add the custom tasks used by the build. Although we could accomplish what we need to using TF commands, these tasks are installed with TFS and work well <Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<!-- These tasks are used by the Team Build process defined in this file -->
<UsingTask TaskName="Microsoft.TeamFoundation.Build.Tasks.DeleteWorkspaceTask"
AssemblyFile="$(TeamBuildRefPath)\Microsoft.TeamFoundation.Build.Tasks.VersionControl.dll" />
<UsingTask TaskName="Microsoft.TeamFoundation.Build.Tasks.CreateWorkspaceTask"
AssemblyFile="$(TeamBuildRefPath)\Microsoft.TeamFoundation.Build.Tasks.VersionControl.dll" />
Add the variables needed for wsp packaging. Note: there is probably a better way to determine the location of the IDE <PropertyGroup>
<!--
Variables added for VSeWSS 1.3 builds
-->
<!-- IDEPath
The path in which DevEnv and TF reside
-->
<IDEPath>C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE</IDEPath>
<!-- TempWorkspaceName
Workspace name, must not match an existing namespace name
-->
<TempWorkspaceName>NightlyBuildTempWorkspace</TempWorkspaceName>
</PropertyGroup>
Add the solution to build. Note: this will build the binaries, but will not generate the wsp file <ItemGroup>
<SolutionToBuild Include="$(BuildProjectFolderPath)/../../Intranet/Intranet.sln">
<Targets></Targets>
<Properties></Properties>
</SolutionToBuild>
</ItemGroup>
Add the target to clean up any files left by the previous build packaging (if the previous build failed or was cancelled). This target is called during the build just before the workspace is created to build the solution <!-- Before the build workpace is initialized -->
<Target Name="BeforeInitializeWorkspace" >
<!-- Delete temporary workspace (if left by previous build) -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Deleting temporary workspace "$(TempWorkspaceName)" (if left by previous build).">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(TempWorkspaceName)"
DeleteLocalItems="true" />
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(TempWorkspaceName)"
DeleteLocalItems="false" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- Error Occurred -->
<OnError ExecuteTargets="MarkBuildStepAsFailed" />
</Target>
Add the error handling target. If any of the other steps fail, they will call this target using ExecuteTargets <!-- Handles custom errors -->
<Target Name="MarkBuildStepAsFailed">
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Failed" />
</Target>
In the AfterCompile target, add the step to delete the workspace that was automatically created by the build <!-- After the solutions are compiled -->
<Target Name="AfterCompile" >
<!-- Delete build workspace -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Deleting build workspace "$(WorkspaceName)".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(WorkspaceName)"
DeleteLocalItems="false" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Delete build workspace -->
Just below that, add the build step to create the temporary workspace <!-- Create temporary workspace -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Creating temporary workspace "$(TempWorkspaceName)".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<CreateWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
BuildDirectory="$(BuildDirectory)"
SourcesDirectory="$(SolutionRoot)"
Name="$(TempWorkspaceName)"
Comment="Temporary workspace">
</CreateWorkspaceTask>
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Create temporary workspace -->
Add the step to package the solution. This first marks all files in the pkg directory as not read only--the packaging will not be able to overwrite these files otherwise. Then it runs the IDE and uses the /package switch to generate the wsp. Finally, it copies the wsp to the drop folder. If you had multiple wsp files to build, you could repeat the steps below or refactor into a target <!-- Place projects to package here -->
<!-- Build and package solution -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Packaging " Contoso Intranet solution".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<!-- The extensions modify files in the pkg directory, so those files cannot read only-->
<Exec Command="attrib -R "$(BuildDirectory)\Contoso\Intranet\Deployment\pkg\*.*" /S /D" />
<!-- Open a second instance of the dev environment and build using /package switch -->
<Exec Command=""$(IDEPath)\devenv" "$(BuildDirectory)\Contoso\Intranet\Intranet.sln" /deploy debug /package" />
<!-- Copy to drop location -->
<Exec Command="xcopy "$(BuildDirectory)\Contoso\Intranet\Deployment\bin\debug\Contoso.Intranet.wsp" "$(DropLocation)\$(BuildNumber)\" /E /Y /R" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Build and package solution -->
Add the build step to delete the temporary workspace we created <!-- Delete temporary workspace -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Deleting temporary workspace "$(TempWorkspaceName)".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(TempWorkspaceName)"
DeleteLocalItems="false" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Delete temporary workspace -->
Check the project back in to source control, and queue the build to test it
|
-
Effective troubleshooting is an important skill not only in the information technology field, but in many other professions. Auto mechanics, electricians, counselors and doctors are essentially professional troubleshooters. They identify problems, theorize solutions from their knowledge and experience, and systematically test them whether they be a new alternator or a prescription for high blood pressure medication. I’m not a doctor, but being a good troubleshooter makes me feel like one. In this article, I’ll share an age-old process for troubleshooting, “RV-AIR,” that was taught to me as an electronics engineer in the armed forces.
Recognize
The first step of effective troubleshooting is to recognize that a problem exists. Obvious, yet often ignored, problem recognition is the single most important troubleshooting step. That’s because the way in which we discover a problem impacts how we fix it, and can be the difference between treating the symptoms and curing the “disease.” Problem recognition can be categorized as reactive or proactive, and describes our capability on a continuum from immature to mature respectively. Reactive recognition is discovering a problem only when someone reports it. Some might call this “fire fighting.” When we are in a reactive state, we don’t know when or from where the next problem will arise, and the time we spend “putting out fires” takes us away from what we want, or need, to work on. On the other end of the spectrum is proactive recognition in which problems are identified as soon as or before they occur. Using the doctor-patient analogy, proactive recognition is a preventative checkup that identifies high blood pressure and prevents a heart attack, and reactive recognition is discovering high blood pressure after having the heart-attack. For software systems, proactive recognition is a health monitoring solution such as Microsoft System Center Operations Manager, but the key concepts of proactive maintenance and health monitoring transcend systems and disciplines.
Verify
Once a problem has been reported either by a person or a system, the next step is to verify it. It is very difficult to troubleshoot a problem that cannot be reproduced, and it is equally frustrating to waste time troubleshooting something that was never broken in the first place. As much as we like our customers, the PEBKAC (Problem Exists Between Keyboard and Chair) factor applies. I’m not saying that user’s are stupid, but experience dictates that you should trust your customers, but not their problem reports. Let me illustrate this point with an example. Your newly licensed teenage daughter calls you in a blind panic that the expensive car you lent her won’t go into gear. What could be wrong? You begin theorizing, “maybe she ran over something,” “maybe when I got my fluid changed, they didn’t put the plug back in.“ Then you jump to a diagnosis and a decision, “The transmission is broken, so I better call a tow truck and make an appointment at the dealership.” You have the car towed to the shop and spend hundreds of dollars to discover that nothing is wrong; your daughter simply forgot to press the brake pedal before putting it into gear! The moral of the story is that problem verification saves time, money and relationships.
Analyze
After a problem has been recognized and verified, the next step is to analyze it. During this step, the symptoms of the problem are analyzed and a set of possible causes, or theories, are identified. The result is a ranked list of potential solutions that will be systematically proved or disproved in the next step. Knowledge and experience plays a large role in how you arrive at your theories and the more troubleshooting you do, the better you will get at it. Strive to identify root causes and permanent solutions. Leverage the experience of others by thoroughly researching the problem on your own first before asking others. Effective troubleshooters are effective researchers, and they know where to look, how to filter out noise and identify useful nuggets of information. From your research and analysis, you will have a list, at least in your head, of the potential causes. Rank the causes from most to least likely and identify a solution for each.
Isolate
After analyzing the problem and identifying the most likely causes and solutions, the isolate step is a systematic process of elimination. For each of your theories, from most to least probable, test the solution in a non-production environment (if possible) so that you don’t introduce new problems. Go slow and apply only one change before each test. Beware of false positives and validate the root problem is solved and not just the symptoms. If you are unable to find a solution, then you may need to collect additional data and return to analysis. Once you find a solution, the next step is to create a plan for applying the fix.
Repair
The final troubleshooting step is to permanently repair the problem by applying the solution identified in the isolate step.
Conclusion
RV-AIR is a proven process for finding and fixing problems and is equally effective for tracking down a memory leak as it is for finding a shorted electrical outlet in your house. The key to effective troubleshooting is to recognize, verify, analyze, isolate and repair. Happy troubleshooting!
|
-
One of the shortcomings of the RSS Viewer web part is that it can only be bound one feed. In this walkthrough, I’ll show you how to make the RSS Viewer web part dynamic with filter web parts and web part connections to enable binding to multiple feeds.
Create the RSS Feeds List
In View All Site Content, select Create
Select Custom List and enter a name such as “RSS Feeds.” Next, add two columns to the list. The first column will hold the feed URL and the second column is a one-line description of the feed that will be displayed in the filter list. Name the column “Feed URL,” set the type to Single line of text (not Hyperlink), make it required, and add it to the default view.
Add the description column. Name the column “Feed Description,” set the type to Single line of text, make it required, and add it to the default view.
Next, add some feeds to the list:
Add an RSS Viewer Web Part
Add an RSS Viewer web part to a page:
Configure the web part to default to your favorite feed:
Add a Filter Web Part
Add a filter web part to the page to control which feed is displayed in the RSS Viewer. Add a SharePoint List Filter web part to the page:
Configure the filter web part to use the values from the feed list:
Connect the Filter Web Part to the RSS Viewer Web Part
Next, connect the filter web part to the RSS viewer web part. Select Connections > Send Filter Values To > RSS Viewer:
Set the Connection Type to “Get Feed URL From”:
Test the Filter
Click the browse button on the filter:
Select one of the feeds and the corresponding feed should display in the RSS Viewer:
Conclusion
The RSS Viewer web part can be made more dynamic and provide a better user experience by using filter web parts. In this walkthrough I showed you one type of filter, but I invite you to investigate other approaches using some of the other out of the box filter web parts.
References and Additional Reading
|
-
One of the new features of Internet Explorer 8 is Web Slices. This feature enables you to subscribe to a section of a web page and notifies you when the content changes. By design, SharePoint Web Parts are a natural fit for this feature, and in this article I’ll show you how to build a Web Part that does just that. I’ll also show you how easy it is to develop a web part using the CTP release of Visual Studio 2008 Extensions for Windows SharePoint Services (VSSWse) 1.3.
About Web Slices
Web Slices enable you to have a very narrow control over the content you subscribe to. When you browse a page that has Web Slice sections, they “light up” when you hover over them:
In addition, the new Web Slice icon on the toolbar lights up, and the menu is populated with all the slices on the page:
When you subscribe to a slice, the web page section is added to the toolbar and is periodically refreshed. The refresh interval is completely customizable and can also be refreshed manually:
Defining a Web Slice Section
Web Slices are defined with HTML tags that have certain CSS classes. You can read the full specification, but here’s all you need to know to build your first Web Slice:
To define a Web Slice section, use the hslice class name:
<div class="hslice" id="1">…</div>
To define the slice title, use the entry-title class name:
<p class="entry-title">Game System - $66.00</p>
To define the slice content, use the entry-content class name:
<div class="entry-content">This auction closes in 4 hours.</div>
The end result looks like this:
<div class="hslice" id="1">
<p class="entry-title">Game System - $66.00</p>
<div class="entry-content">This auction closes in 4 hours.</div>
</div>
Build a SharePoint Web Part
For this example, I’m using VSSWSE 1.3 which you can read about here. I would recommend it not only because it’s easy to use, but also because it is sure to become the de-facto standard. To get started, create a new Web Part project:

Choose if you want to deploy to the GAC or bin directory. We’ll use GAC for this example:
A solution is created with a default web part named WebPart1. Rather than renaming it, delete the WebPart1 folder and add a new Web Part to the project with a more descriptive name:
Next, configure the url of your SharePoint site you want to use to test the Web Part. On the Debug settings, set the start url:
In the Web Part, override the CreateChildControls method:
This code generates the needed HTML and is pretty self-explanatory. I chose to hide the Web Slice entry-title as it would be redundant beneath the Web Part title. To test the code, right-click the solution or project and select Deploy. This will package the Web Part as a feature and deploy and activate it on the site:
Next, add the Web Part to a page in the site and test it.
How I Wished It Worked
Web Slices are powerful, but I don’t really want to develop a bunch of new Web Parts to use that feature. I wish every Web Part were capable of wrapping its content in a Web Slice div tag. I’m sure there is a way to inject this functionality (and there might even be a supported method), but I wish it were built in to the Web Part framework. Every Web Part could then have a Web Slice category with relevant settings:
Wouldn’t that be powerful?
Summary
In this article, I demonstrated how to build a Web Part that users can subscribe to using Web Slices. I also showed you how easy it is to build a Web Part using the CTP release of Visual Studio 2008 Extensions for Windows SharePoint Services version 1.3. Happy slicing!
References and Additional Reading
|
-
Sometimes you need a local copy of an assembly from the GAC and here is a quick tip on how to do it. The GAC can be found in the c:\windows\assembly directory, but if you try to browse it, the following custom shell extension appears:
This view does not provide the ability to copy assemblies, but it does provide some very useful information such as the strong name details. Here are some options to get around that feature to copy an assembly from the GAC.
Option 1: Disable the Shell Extension in the Registry
One possibility, but not the best one, is to disable the shell extension. I don't like this approach because it involves editing the registry and I like the information provided by the shell extension. Here's how to disable the extension if you want to. Open the registry editor and add/set the HKLM\Software\Microsoft\Fusion\DisableCacheViewer DWORD value:
Set the value to 1:
Once you make that change, you can browse the directory:
Option 2: Go Command-O
Another option is to copy assemblies from the GAC from the command line. This approach works well if you prefer working from the command line, but if you like to right-click with a mouse, this might not be the choice for you.
I highly recommend PowerShell, but you can use Windows Command Prompt. Find your way to the c:\windows\assembly directory and copy the file you need:
Option 3: Use the SUBST Command
The SUBST command allows you to create a shortcut to a path on your file system and assigns that shortcut a drive letter. I really like this approach because you have the option of using Windows Explorer without having to disable the shell.
Suppose you want to create a G Drive (G for GAC), use the following command: SUBST G: C:\WINDOWS\ASSEMBLY

Then in Windows Explorer you are free to double-click and right-click to your heart's content and still use the shell extension.

|
-
The SharePoint developer community has produced a wealth of knowledge and code samples that demonstrate how to consume SharePoint web services and leverage them in domain-specific applications. This information is often task-centric, such as "How to add an Item to a List using SharePoint Web Services." In this article, we will take a framework-centric approach and apply sound, fundamental object-oriented (OO) design principles to implement a reusable library for working with the SharePoint web services.
Motivation and Goals
My motivation for this article is to help you create a foundation for a framework that can be used in one, or reused across many SharePoint-integrated applications. I also want to demonstrate how good software design principles can (and should) be applied to SharePoint development, and confess my own sins for not always following them. For some reason, I often find myself thinking procedurally when developing a SharePoint customization. Perhaps it's because the product is a 70% solution and requires a different mindset and approach than a large greenfield software project does. Maybe customers expect results faster when you build it on SharePoint, and I've taken shortcuts to meet them. Or maybe I've just been lazy, but in any case, here are some of the issues I've seen or been responsible for:
- Re-writing the same SharePoint code on each new project
- Not refactoring within and across applications
- Tightly coupling application code to the XML input and output from SharePoint services
- Lack of a domain model; designing procedurally instead of applying domain-driven design (DDD)
- Not having well-defined layers and strategies
- Failure to design for testability and not writing unit tests
Here are some of the goals I would like to achieve with this framework:
- Implement a domain model that is independent and decoupled from the SharePoint services
- Define a set of patterns and strategies for communicating with the SharePoint services
- Create a reusable library that can be used in many different domain-specific applications
- Use DDD and Test-Driven-Design (TDD)
- Apply good OO principals and patterns
- Use Windows Communication Foundation (WCF)
Design
In the next section, I'll walk through the implementation and the process I went through to arrive at the design below. Generally speaking, I used DDD and TDD (where it made sense) and followed the Single Responsibility principle. If you are not familiar with this, it is often described as "a class should only have one reason to change." I call it the "and test." I describe the purpose of the class and if I have to use the word and, I consider refactoring some of the functionality to another class.
In the diagram below, the client application uses the repository and domain classes. The repository is responsible for retrieving domain objects based on criteria. The repository uses one or more service gateways to get the information it needs. The service gateway communicates with the SharePoint web services using a WCF client. The service calls typically return XML responses, and it is the job of the service response mapper to translate the XML to a domain object. Finally, the WCF client factory creates clients configured to call the SharePoint services.
Get the code
Implementation
In this example, the goal is to get all the lists in a given site. Following DDD, we will start the design in the domain layer and create a list class that has a title property:
Create the list repository class to define the method clients will call the get the lists in a given site:
Following TDD, we'll create a unit test that will fail until we implement the repository:
Return to the repository class and add the implementation to call the lists service gateway which we haven't created yet. How did I know to create a lists service gateway? I decided to have a gateway that corresponds to each SharePoint web service and did some research to find the exact service and method that would return the lists for a given site. The implementation below is very simple, but you can imagine a more complex repository that uses several service gateways and does some additional processing to construct a domain object graph.
Create the lists service gateway so the project will compile:
Create a failing unit test for the lists service gateway:

Next we'll add a reference to the SharePoint Lists service. Note: Visual Studio will add an app.config and a Properties > DataSources folder and you can delete both of them. We are going to configure WCF programmatically.
Return to the lists service gateway and fill in the implementation. Now I'm pulling a "Julia Childs" on you. I already went through several iterations of TDD and refactoring to arrive at the implementation below. Initially this method had all the code to call the web service, and there was no WCF client factory nor a mapper. After implementing this logic in several gateways, I refactored the commonality to classes. It made sense to have a factory to construct WCF clients in a consistent way and allow me to make a change in one place if I discovered something new. The Single Responsibility "and test" led me to refactor the mapping code to a separate class and the end result is a much cleaner implementation:

Next, we'll implement the WCF client factory. As you can see, it has been through a few iterations of TDD and refactoring starting with the service url class. This class contains constants and methods for working with SharePoint service urls. The other thing you'll notice is the the WCF configuration that is typically done in a configuration file is being done programmatically in the service binding factory class.
Here is the implementation of the service binding factory:
The gateway uses a mapper to map the XML response from the SharePoint web service to a domain object. The XML response is in the form of <element attribute1...n /> which, in this case, translates nicely to domain objects and properties. The code below uses Linq to find all List elements and for each one it calls MapInternal which creates a list object from the element:
Now if we return to the repository GetLists unit test, it passes and output each list title in the site:
Summary
In this article and walkthrough, I demonstrated how DDD, TDD, patterns and good OO principles can be applied to implement a framework that consumes SharePoint web services. Even though I focused on the web services, the SharePoint API can also be regarded as a service and the same fundamentals can be applied to developing applications that run on the server. I hope this inspires and helps you to implement your own framework and improve the quality and reuse of your SharePoint code.
Get the code
References and Additional Reading
|
-
A common SharePoint customization request is to copy or move items and documents between lists. This can be accomplished without development by creating a workflow using SharePoint Designer (SPD). With this approach, you may encounter issues with list item fields not copying or copying intermittently. This article will explain how the copy list item activity works, what can go wrong and how to troubleshoot and resolve the issues.
About SPD Workflows
If you are not familiar with SPD workflows, here is a quick primer on how to create a workflow that copies a list item to another list. Open the site and select File > New Workflow. Select the source list and set the start options. Note in this example, the workflow executes when a new list item is added.
The workflow consists of steps which have conditions and actions. In this example, there are no conditions, only an action to copy the list item:
Configure the action to copy the current item in the source list to the target list and click Finish to associate the workflow with the source list.
Add a new item to the source list and you can see the outcome of the workflow:
The item was copied to the target list:
How the Copy List Item Activity Works
The copy list item activity iterates through all the fields in the source list and tries to find a match in the target list. The fields are considered compatible for copying when the following is true:
- The source and target field names are the same. It compares the field name you see in the browser (SPField.Title), not the internal/static name (SPField.InternalName)
- The source and target field are the same type
- The source and target fields are compatible. For example, if the source field allows multiple selections, but the target does not, or the source allows selecting people and groups, but the target only allows selecting people
- When the source field is a lookup to another list, the target field must use the same list
- The source field is not read only
- The source field is not a special field, such as ContentTypeId
After the field matches have been determined, a list item is created in the target list using a system update (does not trigger event receivers / workflows and the created by user is system). In addition, when the field type is User, the user(s) are created in the target web if they don't already exist.
What Can Go Wrong
There are two common symptoms and the underlying causes of each is human error. But don't beat yourself up; they are easy mistakes to make and correct. Use the following to troubleshoot the issue:
- When a SPD workflow doesn't copy one field to the target list:
- The field names don't match
- The field names match, but the field type and the settings do not
- In a document library, when none of the fields are copied (except the file name), or when all the fields are copied but intermittently:
- The issue is that everything is working as designed and the workflow hasn't accounted for it! There is a difference between New and Upload
The Difference Between New and Upload in a Document Library
In a document library, users can add a document two ways through the browser. The first option is to click New. This opens the client application associated to the list template such as Microsoft Word. In Word 2007, the list item properties appear on the Document Information Panel (In Word 2003, the list item properties are displayed in a dialog when you save the document):
Because the list item properties are captured by the client application, the document and properties are saved at the same time. The save triggers the workflow created event receiver logic and the copy will succeed. When the document is added using the Upload (or Upload Mutliple) option, the upload occurs in two stages. The first stage is adding the file to the document library:
The second stage is updating the list item properties. At this point, the item has been added to the document library, the created event has fired, and the copy list item workflow has completed. The properties haven't been entered, so the workflow copied the document and blank property values to the target list.
The following summarizes the differences between New and Upload:
- When a document is uploaded using New, there is only a create
- When a document is uploaded using Upload, there is a create when the file is uploaded and an update when (and if) the properties are updated--but the properties are updated with a system update so the update event does not fire
Troubleshooting and Solutions
- When only one field doesn't copy
- Don't trust your eyes, copy the field name from the source list and paste it into the field name of the target list. For example, how long does it take you to spot the difference between these two fields: Source:Mutiline Field, Target:Multiline Field?
- Compare the settings between the source field and target field. Does one allow multiple values and the other does not?
- In a document library when all fields don't copy or copy intermittently
- The workflow needs to account for the difference between New and Upload
- Configure the workflow to execute when the item is changed
- Add a condition to the copy list item step to examine the workflow status
To overcome the issue with document libraries, one possible approach is to modify the SPD workflow as follows:
- Set the workflow to execute only when the item is created
- In the first step, add a wait activity to wait until a required field is not empty (a field that is entered in the "second stage" after the document is uploaded)
Conclusion
SPD workflows are a business-user enabling technology and a useful tool in your SharePoint customization toolkit. I hope this article sheds some light on how to implement a copy list item workflow using SPD and how to troubleshoot and resolve common issues with the approach.
|
|
|
|