Showing posts with label MOSS. Show all posts
Showing posts with label MOSS. Show all posts

Thursday, July 17, 2008

Provisioning monthly folder structure in a SharePoint list

Recently I wrote an article about folder content types in SharePoint and how they can be leveraged to improve the document management in a SharePoint document library. The comments to the article show that there is definitely interest and potential in using folder content types, but they also point out some serious omissions. SharePoint MVP Ivan Wilson has taken things to the next level in a project he independently started on CodePlex. It is a console application that allows you to generate a folder structure within a document library. It will create a folder in the root of the document library using the current year-month (e.g. "2008-05"). 

Check out Ivan's post and the project's home page, this really makes the provisioning of the folders much easier.

Dovizhdane!

Monday, May 12, 2008

SharePoint Folders Need More Love

Folder Content Types for IT Professionals

Published in the May 12, 2008 edition of To The SharePoint

Most of you SharePoint enthusiasts probably know quite a bit about content types in SharePoint. They provide the means to organize metadata in an extremely flexible manner and provide the context for workflows, custom menus, and document templates. However, due to the document centric nature of Microsoft Office and SharePoint, the most commonly used and discussed content types are the document content types. Well, there is another lesser-known character in the content type story of SharePoint--it is the folder content type.

One reason why the folder content type is less popular is that the default SharePoint installation comes with only one of them--the Folder content type. Compared to dozens of out-of-the-box document content types, the Folder is clearly outnumbered. So let's have a closer look at this lonely hero and create a couple of folder content types so that we can find out how to use them to further enhance the user experience and data management of a document library.

To put things into perspective, let's look at how the fictional environmental foundation Rain Forest can use folder content types to improve its excising document library. The foundation staff stores all documents in a document library and they already use several document content types to support their activities. The document types are separated into two functional groups:

  • Project Documents (Additional Fields: Due Date, Assigned To)
    • Application for Grant (Word document)
    • Financial Memorandum (Excel document)
    • Formal Acceptance Document (Word document)
  • Internal Documents (Additional Fields: Contact, Status)
    • Purchase Order (Word document)
    • Invoice (Excel document)

The document library looks very familiar, and all document types are listed in the New menu:

The IT team of Rain Forest also defined some views based on document content type to make the filtering of each document type group easier and to be able to display content type specific fields such as Due Date and Assigned To:

If you are not familiar with document content types, this article shows the basics. For more information on how to create views, check out the following article.

All documents are stored in the root and occasionally employees will create folders at their discretion. However with time the clutter of folders makes locating documents really hard. All users also notice considerable slow down in view performance. After a few months, the root folder contains more than 4000 documents and is expected to grow. What can be done? This is when the little known character from our SharePoint story--the folder content type--comes in to help.

One reason for the performance hit is that folders in SharePoint have some limitations by design. For details on how the number of items affects performance, check out this article. Nevertheless if we partition the documents by financial quarters or other perpetual attributes, we can keep the total number of documents in a given folder within the high performance zone. That’s why we decide to create a folder for internal documents and project documents using the respective folder content types for every quarter of the year.

Furthermore we can provide some structure and boundaries for the employees, so that they cannot create folders anywhere in the document library. To help users locate documents, we’ll use a great feature of SharePoint, which allows us to bind views to a specific folder content type. This will provide context for each folder, so that when a user enters a folder with internal documents, the view will automatically change to display relevant metadata.

First let’s create a folder content type for each of our document groups. The steps are no different than creating any other content type. The only difference is that our content type will inherit from the Folder content type.

You can add specific metadata to each of the newly created content types, but for this walk-through, we’ll use the existing columns.

Next, let’s add the folder content types to our document library:

In addition, we would like to remove the default Folder command in the New menu, so that only our custom folder options are available. To do that, open the advanced settings of the document library and disable the New Folder option.

After these changes we will add our two new folder entries to the New menu.

Now, when a user wants to create a new folder for the next quarter, he or she will select the appropriate folder types from the New menu, and give the folder a descriptive name such as Internal Q1 2008. The process of provisioning a new folder can be automated and extended by using calculated fields or other programming techniques.

To provide the appropriate views, we create one view for the root folder and separate, unique views for each folder content type. The root view will display only folders from the newly defined content types.

Each custom folder content type will have a view that displays metadata specific to the type of document contained in the folder. These folder views are marked as default but are assigned to the specific folder content type.

Let’s see the result by creating a folder of each type.

You’ll notice that if you click the folder Internal Q1 2008, the view automatically will change to the Internal Documents view; similarly opening the folder Project Q1 2008 will change the view to the view Project Documents.

To add additional context sensitive behavior, you can also limit the New menu items displayed for each individual folder, so that only Internal documents show in the New menu of the corresponding folder. From the drop down menu of each folder select Change New Button Order, and hide the appropriate document types.

When you enter the folder you’ll notice that only the contextually correct New menu items exist.

Similarly you can hide the documents from the new menu of the root folder. Open the document library settings and in the content type section click Change new button order and default content type. Hide all but the folder content types.

From now on, the dedicated volunteers of Rain Forest can rest assured that they can locate documents easily and that the performance of their document library is going to be stable. Furthermore, the IT Pro of the foundation has some great ideas about how to add custom menus for each folder content type, so that actions applicable to all documents in a folder can be executed faster and in the proper context. There are also many opportunities to use item event handlers and the SharePoint DOM and workflow with folder content types to further extend the application. This SharePoint story certainly does not end here.

Mikhail Dikov is a senior software engineer at Global 360 (www.global360.com) and MVP for Microsoft Office SharePoint Server with background in CMS and BPM software. Mikhail brings more than 8 years experience in Microsoft technologies such as .NET, ASP.NET and more than 12 years of IT experience. Current interests include BPM, BI, SharePoint, .Net and AJAX. Mikhail is frequent speaker at code camps in Florida and an active member of the Space Coast Dot Net User Group (www.scdnug.org). Email: mdikov at gmail dot com Blog: www.mikhaildikov.com

Thursday, March 20, 2008

MOSSMOSIS - Orlando

This Month: Intro to SharePoint Designer

When / Where?
Wednesday, April 2, 2007   - 6:30 PM EST

Orlando Public Schools Administrative Offices
445 West Amelia Street
Orlando, FL 32801 – 1129

How to sign up?
http://www.clicktoattend.com/?id=126847

Who Should Attend?

Developers, designers, power users, architects,
administrators.

What will be covered?
In this session we will dive into SharePoint Designer. 

Who will be speaking?
Scott Schwarze

Disappearing web.config entries

How many times have you experienced a chilling moment when something goes terribly wrong with the system you just touched and you don't have any clue what would've caused it?

In a SharePoint installation with multiple web applications and several custom solutions there may be a lot of action going on in web.config files. Even the slightest validation error in these files will bring the web application to a halt. This and the fact that the SPWebConfigModification class has a will on its own make the task of coordinating web.config modifications a very touchy business.

Recently one of my colleagues reported that after installing one of the SharePoint solutions, entries installed by another solution were disappearing, leaving the web application in chaos. Logically I started poking the features in the SharePoint solution, which was "causing" the issue, but this lead me to no where.  I only learned that when you call:

webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

The web.config files for all web applications get rewritten, regardless of which web application is being updated. But this turned out to be a "feature" of SharePoint. Then I started investigating what other web.config modifications are being created by the rest of the solutions on this server. Luckily most of these belong to our company, so I was able to pull up the code. All features worked correctly when executed separately, but still in a particular sequence some of the web.config modifications were disappearing. And there it was ... one of the features was adding the modifications correctly:

SPWebConfigModification modification = new SPWebConfigModification();
modification.Path = "...some path..."
modification.Name = "Example"
modification.Value = value;
modification.Owner = "Owner"
modification.Sequence = 0;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
webApp.WebConfigModifications.Add(modification);

then applying the changes to update the web.config:

webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

But there was no webApp.Update() to persist the changes in the SP database!

It turns out it is very easy to omit this part, because when you develop or debug such feature all will work fine until something does not flush the application pool thus disposing off the newly created SPWebConfigModification. The next solution or feature that calls ApplyWebConfigModifications will force reapply all modifications pulling them from the SharePoint database. For some features this actually might be a welcome side effect, but unless this is not the case you need to call webApp.Update() to permanently save the modifications to the SharePoint database.

One mystery solved. Next, please!

Dovizhdane!

Orlando Code Camp - Sold Out!

Just noticed that the Orlando Code Camp is sold out. This is going to be another super-charged and totally free event organized by our friends at ONETUG.org. I signed up as a speaker with my two sessions from South Florida Code Camp. They were very well received in South Florida, so after some adjustments I decided to give them one more run. Come with your experience and ideas and lets talk about how we can avoid some common frustrations in SharePoint development.

I'll be carpooling with some Brevard developers, so if you need a ride or you want to save on gas contact me today or tomorrow to give you the details.

For details: http://www.orlandocodecamp.com/

Dovizhdane!

Tuesday, March 04, 2008

Unable to add selected web part(s).

Every once in a while when you create a web part or upgrade web part, or do some of things developers do when developing web parts, there comes the chilling moment, when you see an error message such as the one below:

So the question is what do we do in this case? Before you hit the discussion boards, or worse, start pulling your hair, here are couple of tips you can use to troubleshoot the issue:

  1. Make sure the control is registered as safe in the web.config (duh...actually the error says what it means, right)
  2. Make sure the assembly is accessible and in the [port]bin folder. (obvious, but worth mentioning)
  3. Make sure the assembly name in the *.webpart definition file, matches the assembly name in the safe control element in web.config
  4. Make sure you don't have more than one *.webpart file for the same web part in the web part catalog. This may happen if you changed the name of the *.webpart file.
  5. Restart IIS to start clean. Attach the debugger to the w3wp.exe process and try to load the page with the rogue web part. This way you can determine the exact location of the assembly you are loading.
  6. Check if the web part class exists by opening the assembly with reflector. This might sound funny, but in a bigger team, when different versions of assemblies are flying around it is very easy to overlook something and to use the wrong version, which so happens does not contain the web part class at all.
  7. If you have other tips or suggestions, please add them as comments.

Phew, I think I dodged that one... It turned out I got an older version of the assembly and my web part class was not even there.

Dovizhdane!

Unable to add selected webpart(s). A Web Part or Web Form Control on this page cannot be displayed or imported. The type could not be found or it is not registered as safe.

Monday, February 04, 2008

South Florida Code Camp 2008 - Recap

What a great event that was! The FlaDotNet user groups put together their 4th code camp with absolute ease (or at least they made it look like this) and with lots of sessions to choose from.

For those who came to my sessions, one big "Thank You!". Please follow the link at the end of the post to download the presentation slide deck and the sample code.

After I finished with my "work", which was in the first two time slots, I stayed in the SharePoint track for a presentation from Michael Lotter - InfoPath 2007 and Visual Studio 2008. This was a very well prepared and presented session and it gave me a good understanding of the moving parts involved in an InfoPath based solution.

In the afternoon I ventured in a non SharePoint waters. First Mark Miller got me hooked on CodeRush and Refactor with High Speed Development in Visual Studio with CodeRush and Refactor. Then Bill Reiss did a great preview on Silverlight 2.0 with some cool videos. I finished with the passionate presentation by Larry Port on Continuous Integration with CruiseControl.Net and Nant.

The after-party was a hit. Lot's of good food, drinks and smart people to talk to.

So, here are the links:

Slide deck 1

Slide deck 2

Code Sample

Dovizhdane!

Friday, January 25, 2008

South Florida Code Camp 2008

Yet another totally free event organized by developers for developers in South Florida will take place next Saturday, February 2nd, 2008. Dave Noderer and crew filled up the agenda with 72 sessions (That's right 72!) divided in 12 tracks. For full information go to http://codecamp08.fladotnet.com/.

On this code camp I have two sessions. One topic, split in two parts. The somewhat clunky name Utilizing Visual Studio 2008 capabilities for better SharePoint Development comes as a result my work on several projects in the last months and some of the exiting new features of Visual Studio 2008. I tried to find an answer to questions such as:

  • How to use Visual Studio Web Designer to create certain types of SharePoint UI elements?
  • How to structure my projects, so that I can easily test the components outside of SharePoint?
  • How to structure my projects and what community tools to use, so that I have to think less about the process of creating SharePoint solution files?

These are all big questions when it comes to the transformation of SharePoint to an actual development platform. To answer these an other challenges of Sharepoint development I am going to demonstrate how to integrate an existing information system with SharePoint without compromising quality or scalability. The four topics I am going to address are:

Part 1

  • SharePoint infrastructure, or how to reduce the time and maintenance of SharePoint specific deployment and plumbing.
  • UI design, or how to use Visual Studio 2008 web designer and new CSS features to easily create SharePoint layouts pages and web parts.

Part2

  • Testing SharePoint solutions, or how to take most out of the newly added testing capabilities in Visual Studio 2008 Professional.
  • ASP.NET AJAX Extensions in SharePoint, or how to use it and how to automate the configuration of this ASP.NET extension.

To spice things up Apress provided several copies of Workflow in the 2007 Microsoft Office System by David Mann.

So if you are in the area come and join the geek crowd. Here is the location:

Devry University
Maps.live.com
2300 SW 145th Avenue Miramar,

FL 33027

Dovizhdane!

Thursday, January 03, 2008

Microsoft MVP Award

A day ago I received an E-mail from Microsoft that I have received the 2008 MVP Award for Microsoft Office SharePoint Server. What a great start of the new year! This award is a great recognition for my SharePoint sessions on several code camps in Florida, my blog and other work I have done in the developer community. It is also a great motivation for what I have in mind for the upcoming 2008.

I would like to thank Ken Tucker, Joe Healy and the Florida developer community for organizing many great events and ultimately giving an opportunity to local developers like me to grow professionally and have fun doing it.

In the next couple of months I plan to build upon my experience in 2007 and attend several code camps as attendee and speaker. In addition I'll be looking into opportunities to write more extensively as a book reviewer, technical editor or author. Apress and other publishers have great user group programs, which I hope will bring me closer to this goal. I also plan to work on the much needed makeover of my site. I was postponing this for a while, but finally I am going to take Verio's generous web hosting offer for Microsoft developers and create a site that better meets the needs of my work.

Looks like a lot of fun, doesn't it!

For more information about the MVP Award visit: http://mvp.support.microsoft.com/gp/mvpintro

Dovizhdane!

Thursday, December 13, 2007

Comprehensive guide for MS Office and SharePoint integration developers

The book Pro SharePoint solution Development, Combining .Net, SharePoint, and Office 2007 written by Microsoft insiders Ed Hill, Susie Adams is an indebt review of how to utilize SharePoint in the Microsoft Office ecosystem. This book from the Pro series of Apress delivers a variety of advanced examples, richly illustrated with sample code, downloadable from Apress, and step by step instructions and illustrations in the book itself.

The organization of the book is very convenient and the first four chapters allow the reader to brush up his knowledge about MS Offices and SharePoint with abundant external links. Each consecutive chapter after that represents a standalone example based on a simplified real-world scenario. The examples are focused on the integration with a particular MS Office product. For example Chapter 5 demonstrates a scenario where MS Word integrates with SharePoint and Chapter 9 shows how to construct PowerPoint slides using content stored in a SharePoint list. Every example starts with an introduction and walkthrough, which allows the reader to start reading the chapter directly without losing context.

The complexity of integrating products of the MS Office family in enterprise solutions requires quite a bit of knowledge and experience thus I do not recommend this book to beginners in SharePoint and MS Office programming. While this book has a plenty of introductory and historical information about MS Office development and SharePoint customization, it does not emphasize on important steps of professional SharePoint development such as creation of SharePoint solutions, list and site template customization and provisioning. However if you are already familiar with SharePoint (WSS 3, MOSS 2007) concepts such as solutions, features, workflow etc., this is the book to put all these features in the context of enterprise applications. Since I started working on an integration project with MS Office and SharePoint at the time I was reading the book, I took away plenty of ideas to use in my project.

The software and hardware requirements for the examples in this book are quite high, so if you want to be able to implement them on your own you need to allocate some time to prepare a system with MOSS 2007, MS Office 2007 Enterprise, VS 2008 Professional or Team Edition and for the first example MS Office 2003. In addition there are several manual actions, which require a bit more time. Something, which may not be obvious from the title, is the heavy use of the new MS Office document standard - Office Open XML (commonly referred to as OOXML or OpenXML). This was my first exposure to this format specification and I found its use throughout the book very useful.

Overall this book is of great value to intermediate and advanced developers, working on enterprise applications based on the MS Office system or integration projects with third party vendors. The examples can be read independently and each one of them not only demonstrates the implementation of a particular scenario, but also provokes ideas for other projects.

Dovizhdane!

1590598083, 978-1590598085

Tuesday, November 20, 2007

Support multiple scopes in SPEventReceiver class

Quite often when I create a feature SPEventReceiver I am not certain what would be the best scope to use it. In other situations I am copying existing code from a feature scoped differently than the newly created one. After couple of these you get tired of trying to find out what is the actual meaning of the properties parameter, as it changes with the scope change.

So here is a little snippet that I started using to save some time and effort. In this particular case I am fetching the current web application from the feature properties:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWebApplication app = null;

if (properties.Feature.Parent is SPSite)

{

SPSite siteCollection = properties.Feature.Parent as SPSite;

app = siteCollection.WebApplication;

}

else if (properties.Feature.Parent is SPWeb)

{

SPWeb web = properties.Feature.Parent as SPWeb;

app = web.Site.WebApplication;

}

else if (properties.Feature.Parent is SPWebApplication)

{

app = properties.Feature.Parent as SPWebApplication;

}

else if (properties.Feature.Parent is SPFarm)

{

throw new Exception("Not supported as farm feature.");

}

}

This code can be used in some of my previous posts:

SharePoint Resources, Types, Use and Deployment (Update)

SharePoint Resources, Types, Use and Deployment

Dovizhdane!

Thursday, November 01, 2007

Using ASP.NET AJAX extensions in SharePoint WebParts

As I mentioned in my previous post I a while ago I started working on a project with AJAX enabled WebParts. I already have been playing with AJAX and also working with SharePoint and WebParts in my previous projects at G360 and in the .Net community. Nevertheless I did not get a chance to work with both technologies in one project, so there you go, all my wishes came true.

With the release of ASP.NET AJAX and the expected support in SharePoint SP1 it makes good sense to continue using this technology instead of other alternatives such as AJAX.NET. One of the biggest advantages is that ASP.NET developers have the choice to use UpdatePanel and the control toolkit to upgrade existing applications or develop new one without a major learning curve requirements. The UpdatePanel control encapsulates a lot of functionality and allows developers to continue to use the ASP.NET controls and programming style, but overrides the post-back events with an asynchronous call and partial update of the area covered by the UpdatePanel. Developers also have the option to go into more sophisticated AJAX development techniques using directly the client and server side AJAX libraries. With coming support for JavaScript Intellisense and debugging in Visual Studio 2008, this option will be increasingly more attractive, but in Visual Studio 2005 these luxuries are not available.

When it comes to using ASP.NET AJAX in SharePoint nothing is written in stone, but one big chunk of the mystery is solved in this post from Mike Ammerlaan - http://sharepoint.microsoft.com/blogs/mike/Lists/Posts/Post.aspx?ID=3. In his post Mike describes the steps, but there is still some work to make this function properly in a solution that can be deployed to a customer. One of the issues is how to make the web.config changes using SharePoint Feature. The solution comes from the SharePoint 2007 Features project on CodePlex, which contains a feature that does exactly that. Another issue is that to add the script manager we don't really want to edit to the master pages. Even though some don't recommend this technique, I found that adding the ScriptManager dynamically worked well for my project and greatly simplified the deployment. This is the code I used:

protected override void OnInit(EventArgs e)
{
base.OnInit(e);

// Let's find if the ScriptManager exists and add it if not
scriptManager = ScriptManager.GetCurrent(Page);
if (scriptManager == null)
{
scriptManager = new ScriptManager();
if (Page.Form != null)
{
// Insert script manager after the web part manager
for (int controlIndex = 0; controlIndex < Page.Form.Controls.Count; controlIndex++)
{
if (Page.Form.Controls[controlIndex].GetType() == WebPartManager.GetType())
{
Page.Form.Controls.AddAt(controlIndex + 1, scriptManager);
}
}
}
}
}

Using ASP.NET AJAX Control Toolkit was also very straightforward. Most of the controls worked properly in SharePoint. I did not have the chance to test all of them, but it looks like controls that iterate many objects on the page and make style modifications to many controls work somewhat slower, if at all, in the default SharePoint page. The validation extender, for example, sometimes does not position well the call out next to the field, or the modal pop-up mocks up the whole page and causes high CPU utilization. Nevertheless there are plenty controls that work well. One of the controls I use frequently is the tabbed control, which is very handy for configuration pages.

One thing I found cumbersome is to use UpdatePanel and some of the other AJAX enabled template controls directly from server side code. I know a lot of web control and WebPart developers have long forgotten the design-time environment of the web forms, but would it be nice to be able to use a visual tool to design WebParts and to be able to easily test them? I hope I'll find some time to write about the solution I worked out for my projects. Until then ...

Dovizhdane!

Thursday, October 11, 2007

MOSSMOSIS Orlando

Recently Scott Schwarze from the Orlando chapter of MOSSMOSIS invited me to talk about SharePoint WebParts and related issues. I was trilled to do this since I already started an AJAX project and it was great to have an opportunity to share some of my experience. The title of the talk was "SharePoint Web Parts - from Hello World to ASP.NET AJAX extensions", in which I gradually build up an AJAX enabled SharePoint WebPart and functional web tests for it.

MOSSMOSIS secured a great venue for their meetings (the downtown Orlando building of the Orange County School System) and provided excellent snack.

In addition I would like to thank all the MOSSMOSIS members, who came to the yesterday's meeting. You can download the slide deck and the samples from the links below:

SharePoint Web Parts - from Hello World to ASP.NET AJAX extensions:

Presentation slide deck

Code Samples

Dovizhdane!

Monday, July 16, 2007

Tampa Code Camp

One big Thank You to all of you that attended my Tampa Code Camp session "Developer's Introduction to SharePoint". The presentation and the code sample are available for download here : TampaCodeCamp2007.zip. If you have questions related to the sample post a comment to this message.

The code camp was really big. The attendance was well above 300 and probably reaching 400. Keith Kabza and his team from the Tampa Bay .NET User Group did an amazing job organizing the event. The venue was loaded with modern AV and the logistics of the sessions, the lunch and ... the parties was flawless. Simply put, another great Florida code camp.

Dovizhdane!

Wednesday, July 11, 2007

RunWithElevatedPrivileges, watch out for the site context

Soon or later most SharePoint developers use RunWithElevatedPrivileges. We do that either to grant temporary access to files and folders not accessible by the current user or to execute some other action, which requires higher privileges. One thing I constantly forget about that always gets me with an "Access denied" error is when I omit the fact that the SPSite/SPWeb objects are initialized differently for different user contexts. Have a look at this code:

SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPWeb web = SPContext.Current.Web;

// do something that's not allowed for the current user, but it is ok for the elevated user

SPFile file = web.GetFile(restritedFileUrl);

string readProperty = file.Properties["Name"].ToString();
});

Seems like done deal. Not so.... The problem is that the SPWeb object from the current context is initialized using the current user's credentials, so even though we run the code snippet with elevated privileges the actual access to the file is still restricted. To circumvent this we have to create a new SPSite/SPWeb object within the elevated code, where we run in the context of the elevated user. Then we can perform the restricted action. Since the SPSite/SPWeb objects are created explicitly we have to also dispose of them (more about why we need to do this here). The modified working code becomes:

string siteUrl = SPContext.Current.Web.Url;

SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPSite sysSite = new SPSite(siteUrl);
SPWeb sysWeb = sysSite.OpenWeb();

// do something that's not allowed for the current user, but it is OK for the elevated user

SPFile file = sysWeb.GetFile(restritedFileUrl);

string readProperty = file.Properties["Name"].ToString();

sysWeb.Dispose();

sysSite.Dispose();

});

Access granted...

Dovizhdane!

Saturday, June 09, 2007

Using satellite assemblies in SharePoint applications (with a twist)

What I like about the way SharePoint stores localization resource files is that all strings are stored in central location. This makes the maintenance a bit easier (you don't need to hunt down all files that need translation) and allows ad-hoc changes without the need to recompile. On the other hand it's kind of hard to make strongly-typed notation (Resources.[file prefix].[resource name].) work and the time when the resources are auto-compiled can be a bit unpredictable.

To find a solution, which allows me to create centralized language assembly for all strings and still be able to use strongly-typed notation I looked at another feature of the .Net framework - the satellite assemblies.

Create a resource file in an assembly (e.g. Strings.resx), then add language specific version (Strings.en-us.resx), and in the build folder the compiler will create a sub folder with the locale id to store language a specific extension of the assembly. The limitation of this approach is that the automatically generated class is declared as private thus we cannot reference the resources from different modules in our SharePoint application.

Trying to find a solution to this limitation I noticed one feature of the tool ResGen.exe, which compiles the resources. The command line parameter /publicClass allows us to generate the resources class as a public class.

To make this work I added the following line to my pre-build commands:

ResGen.exe /str:cs,<namespace> /publicClass "$(ProjectDir)Strings.resx"

Then I added the main assembly and all satellite assemblies to the SharePoint solution manifest. In addition the rest of the application modules can use the localization assembly simply by adding a reference to it.

<Assembly DeploymentTarget="WebApplication" Location="Localization.dll" />
<Assembly DeploymentTarget="WebApplication" Location="en-USLocalization.resources.dll" />

Compared to the use of global resources this approach does not allow the use of explicit expressions <%$ Resources:[filename prefix,]resource-key %> in ASPX pages, but I found that this is not a big limitation, especially if the development style of your team is more server side oriented.

Dovizhdane!

Wednesday, May 30, 2007

Why use SPJobDefinition in SharePoint Resources deployment

Recently Matthias Glubrecht and other readers of my post SharePoint Resources, Types, Use and Deployment asked me why do I use a custom SPJobDefinition to deploy resx files to the App_GlobalResources folder. The reason is simple (I remember when I asked Vincent Rothwell the same question and he was kind to explain this to me), the event receiver events (FeatureActivated etc.) are only invoked on the server where the feature gets activated not on all farm servers.

In other words if we need to make modifications to the objects stored in the content or configuration databases of the SharePoint farm, we can use directly the event receiver event handlers. The situation is a bit different when we need to modify objects outside of the SharePoint database, such as the files system, files or the resources (resx) files. In order to invoke the update on all front-web servers need to create a custom SPJobDefinition. One exclusion of this rule is the web.config modification class, which synchronizes web application configuration files via the SharePoint DOM.

Another benefit of using SPJobDefinition is that the timer service runs with a higher privileged account, where as the event receiver event handlers are invoked in the context of the current user.

Dovizhdane!

Wednesday, May 16, 2007

Creating Dynamic Breadcrumbs in SharePoint ASPX Pages

If you already played with custom layouts pages in SharePoint you probably noticed that the breadcrumbs are missing. In a recent post Vincent Rothwell explains how to enable the breadcrumbs in ASPX pages placed in the LAYOUTS folder of SharePoint. Since the stock layouts pages use the SPXmlContentMapProvider, which in turn uses the layouts.sitemap file located in the _app_bin folder, if we add entries for the newly added layouts pages we'll get a rudimentary breadcrumb fixed to one level under the site node. The breadcrumb is in this format: site > custom page. This is one good solution and works fine if this format is satisfactory for your project.

However this is not helping us if we need a dynamic breadcrumb, which will change to represent the logical structure of the site. For example if the ASPX page handles a list item in a specific way, I would like the breadcrumb to properly display the breadcrumbs. For example site > list > folder > item > custom page.

One way to go is to create a custom SiteMapProvider, but this requires changes in the master pages and a bit more code. I was looking for a solution, which works with the standard applications.master page and can be used to modify the breadcrumb of any layouts page.

Another solution is to attach to the SiteMapResolveEventHandler. This is not a secret and there are many samples for regular ASPX pages (Raj thanks for pointing this out!), but in SharePoint context there are several twists, which I would like to illustrate with this sample.

First let's attach our event handler to the provider and do some parameter validation.

private SPWeb web;

private SPListItem currentItem;

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

SiteMap.Providers["SPXmlContentMapProvider"].SiteMapResolve +=

new SiteMapResolveEventHandler(provider_SiteMapResolve);

string listId = Request["listId"];

string itemId = Request["itemId"];

if (string.IsNullOrEmpty(listId) && string.IsNullOrEmpty(itemId))

{

throw new Exception("Invalid item ID or list ID.");

}

else

{

web = SPControl.GetContextWeb(Context);

SPList list = web.Lists.GetList(new Guid(listId), true);

currentItem = list.GetItemById(int.Parse(itemId));

}

}

Since the SiteMapResolve event applies to all pages we want to make sure that we detach the event handler:

protected override void OnUnload(EventArgs e)

{

base.OnUnload(e);

//detach from the static SiteMapResolve event and restore localization mode

SiteMap.Providers["SPXmlContentMapProvider"].EnableLocalization = true;

SiteMap.Providers["SPXmlContentMapProvider"].SiteMapResolve -=

new SiteMapResolveEventHandler(provider_SiteMapResolve);

}

Because this event gets fired on all pages that subscribe to the event, we want to make sure that our code alters only our page. This is why we add the IsSamePage function, which can apply different criteria to determine whether our page is currently loading.

/// <summary>

/// Determines whether the two contexts are equal and, therefore,

/// whether the SiteMap.SiteMapResolve event should be fired.

/// </summary>

protected virtual bool IsSamePage(HttpContext context1, HttpContext context2)

{

//by default, the contexts are considered the same if they

//map to the same file and the same listid/itemid

return ((Server.MapPath(context1.Request.AppRelativeCurrentExecutionFilePath) ==

Server.MapPath(context2.Request.AppRelativeCurrentExecutionFilePath)) &&

(context1.Request.QueryString == context2.Request.QueryString));

}

Now lets do the real work and modify the breadcrumb. In this sample I only add the name of the list, which contains the item and the name of the item itself. A logical extension is to add the folders in the path of an item, but once you get the idea you can change the code the way it fits your needs. The event handler should return the last node in the tree. If an exception occurs, we just ignore all changes.

/// <summary>

/// Overrides the breadcrumbs with a new one in the format

/// [site]>[repository list]>[process folder]>[page title].

/// </summary>

SiteMapNode provider_SiteMapResolve(object sender, SiteMapResolveEventArgs e)

{

if (!IsSamePage(Context, e.Context) || currentItem == null)

{

e.Provider.EnableLocalization = true;

return e.Provider.CurrentNode;

}

SiteMapNode pageTitleNode;

SiteMapNode listNode;

try

{

// Trun off localization, so that the custom nodes do not get overwritten

e.Provider.EnableLocalization = false;

// Clone some node to get a valid node object and modify it.

listNode = e.Provider.RootNode.ChildNodes[0].Clone();

listNode.Url = currentItem.ParentList.DefaultViewUrl;

listNode.Title = currentItem.ParentList.Title;

listNode.ChildNodes = new SiteMapNodeCollection();

// Add as many new nodes as you need here

pageTitleNode = new SiteMapNode(e.Provider, Guid.NewGuid().ToString());

pageTitleNode.Title = Title.Text;

listNode.ChildNodes.Add(pageTitleNode);

pageTitleNode.ParentNode = listNode;

}

catch (Exception ex)

{

return e.Provider.CurrentNode;

}

return pageTitleNode;

}

There are two tricks in this code that I want to point out. The first one is that we cannot add a new root (breadcrumb) node to the provider since the property is read only and our page does not have an entry in layouts.sitemap. This is why we use any of the page nodes defined in the layouts.sitemap file (thus eliminating the need to alter layouts.sitemaps). Since the default format is site > custom page, we get the site node automatically. What we need to do next is to modify the leaf node and extend it with as many node levels as we need. The second caveat is the line where we disable provider localization. This is needed, so that the modified text does not get overwritten.

In the next lines we overwrite the URL and the text for the first node with the URL and the name of the list, which contains the item. Next we add an additional node to display the name of the item.

To make things even easier take out the class from the code-behind file and compile it in a separate assembly, then reference this class from any "List Item" page to get the same breadcrumb behavior.

And finally a link to the source code.

Dovizhdane!

Tuesday, April 10, 2007

Where are my SharePoint features?

On several occasions colleagues and some of my blog readers asked me questions about how to locate a particular SharePoint (WSS v3 or MOSS 2007) feature. In most cases the feature was placed correctly in the FEATURES folder in the 12 hive, but they couldn't find it in the SharePoint UI thus they were not able to activate it. Since this happened more than three times it calls for a good old blog post. In this post I am going to write about the different types of features and how to access them from the SharePoint UI.

Let's assume you already deployed your feature using either a SharePoint solution or by using stsadm -o installfeature -filename [feature folder]feature.xml . The feature files are located in the folder ..\12\TEMPLATES\FEATURES\[your feature name]\. So you followed the SDK or the sample and it looks like all is fine until you open the Site Features page of your target web site and the feature is not listed there. What's going on?

It all depends on what is the scope of your feature. There are four feature scopes, which deploy the feature on four different levels in the farm hierarchy and as a result are managed from different web pages within the site settings and the SharePoint Central Administration.

The Site scope installs the feature on a site collection level, which makes it available only on the Site Collection Features page accessible from the Site Actions menu. This menu however is only available for the root site of a site collection and is not displayed in the sub sites, which is a typical source of confusion.

The Web scope installs the feature on every web site including the root of the site collection in the Site Administration section as shown above.

The next scope level is WebApplication. These features are applied to all sites in all site collections of the web application and they can be activated and deactivated from Central Administration > Application Management > Manage Web Application Features.

Finally the features with the Farm scope are are accessible from Central Administration > Operations > Managers Farm Features.

No more lost features...

Dovizhdane!

Thursday, March 22, 2007

SharePoint Resources, Types, Use and Deployment

In the recent weeks one of my tasks was to work on the localization of a WSS solution and deployment of resources to WSS sites. I found that this topic is still not well explained and there are not too many sources that describe the specifics of using and deploying resources in WSS 3. In this post I'll explain the differences in the use and deployment of several types of resource files.

Types of WSS Resources

There are two groups of resource files in WSS. The first one includes files used during provisioning of sites and features. This group I'll call provisioning resources. The second group includes resource files used by ASPX pages and assemblies at run-time. This group respectively I'll name run-time resources.

The provisioning resources are located in the 12 hive in ..\12\Resources. In WSS the default provisioning resource file is core.resx. This file is used in site definitions (one.xml) files, features, list definitions etc. For example you can open any of the existing site definitions to see how this works. The picture below shows the first rows of the ONET.XML file of the Wiki site definition.

In a WSS feature there are three options for using localized strings from resource files. The default location for the feature's resources is ..\12\TEMPLATE\FEATURES\[FeatureName]\Resources\Resources.[Culture].resx and the strings can be referenced with $ Resources:resource-key, omitting the resource file prefix. Another option is to use the resources in the ..\12\Resources folder, which are available with the full explicit expression form $ Resources:[filename prefix,]resource-key, where the resource file name prefix is specified. The file name prefix can be omitted when the attribute DefaultResourceFile contains a name prefix of a file in ..\12\Resources folder. The attribute RequireResources of the feature element provides additional control over the activation of localized features (see WSS SDK for more info).

Nevertheless the provisioning resource files are not accessible from the ASP.NET web sites. This is where the run-time resources come to play. These files are used in vanilla ASP.NET sites and are located in the web application folder under ..\wss\VirtualDirectories\[port]\App_GlobalResources. They are compiled automatically by the .Net framework in the Resources namespace. These resources are available in assemblies and directly in ASPX pages. To access them from the code use the strongly-typed notation Resources.[file prefix].[resource name]. In ASPX pages use the explicit expression <%$ Resources:[filename prefix,]resource-key %>. The default WSS resource file in this category, used by all WSS sites, is wss.resx.

Deployment of Provisioning Resources

The deployment of provisioning resources can be accomplished using WSS solutions. Unfortunately neither of these deployment facilities support the deployment of run-time resource files directly to the App_GlobalResources folder. In the following samples I'll demonstrate how to access and deploy both types of resource files.

The WSS solution manifest contains two elements with names that imply to have something to do with the deployment of RESX file. They are <ApplicationResourceFiles> and <Resources>. Unfortunately they are not really helpful and basically can be ignored.

To deploy provisioning resources available to all site definitions, features, list definitions etc. use the <RootFiles> element in the WSS solution manifest. The target folder for this element is the 12 hive ..\12\. The files in the example below will be copied to ..\12\Resources\.

To deploy feature specific provisioning resource files, inside your feature's folder, create a sub folder named Resources and rename the resource files to Resources.[language-locale].resx. Remember to omit the file prefix in the string expression ( e.g. $Resources:resource-key; ).

Deployment of Run-Time Resources

Global WSS run-time resources reside in ..\12\CONFIG\Resources. This copy of the files is not used directly. At the time a new web application is created the content of this folder is copied over to ..\[port]\App_GlobalResources. If your organization has one set of localization files used in multiple applications this is the place to put them. You can use the <RootFiles> element in a similar way as described above to deploy such files using WSS solution. However it is very rare that a new web application will be created for a single site or solution. Another option is to use the use stsadm command with the option copyappbincontent to copy the content of ..\12\CONFIG\Resources (among other files and folders) to App_GlobalResources.

None of the above described approaches allows developers to deploy resource files at the time the site or the feature are provisioned. To circumvent the lack of such functionality I hooked up a custom SPJobDefinition to the FeatureActivated event of its event receiver. This is becoming common practice, when it comes to extending the WSS solutions and WSS features. (I got inspired by Vincent Rothwell and his post about extending breadcrumb for pages in _layouts). The job copies the files from the feature's folder to the App_GlobalResources folder of the current web application. Here is how it works.

First let's create a feature to deploy the resource files. The feature defines the event receiver and the assembly.

Next let's create our custom job definition. The constructors look like this:

The first constructor is required and used internally. In the second constructor we simply initialize the source path using the feature's physical path.

The next step is to define the overloaded Execute method, which will copy the files from the feature's folder to the App_GlobalResources.

Once we have the custom