Wednesday, July 18, 2007

Trying to use a closed/disposed webpart.

Coming from a C++ background, I am a stickler for making sure my objects are disposed. I also know that cetain SP objects are extremely heavy, and should always be tossed when you are done with them. These objects include SPWeb and SPSite.

An error I know some developers run into is "Trying to use an SPWeb object that has been closed or disposed and is no longer valid." Let me try to shed some light on this, and also point out the correct way to create these objects.

There are 2 common ways to get our hands on an SPWeb object. One way is to use the SPSite.OpenWeb() method and the other is to use the SPContext.Current.Web method. Both of these methods will return to you the current Web, but the difference is that OpenWeb will return a Web object in a different memory space and CurrentWeb will return the Web object using the current memory space and thread.

As .net 2.0 developers, we are told to wrap our disposable objects in Using clauses, or (correctly) to use try/finally. The problem is that when you dispose of the SPWeb object after you gained a pointer to it from the SPContext, you are disposing of the object being used to render the page. Seldomly, Sharepoint recovers from the error, and goes on it's merry way. But more likely, the page will not render, and you will get the error.

As a best practice, if I am only going to fetch and display data, I use the SPContext object and do not dispose of the object, and when I plan on modifying or changing data, I use OpenWeb and dispose.

We all know that SharePoint tries to cover errors, and if a webpart function catches the error, but does not display it, then you can run into a situation where a container page is failing for an unknown reason. Your best bet is to try to add/remove the webparts on the page until the offensive webpart is discovered.

My friend Eric Stallworth points out that Microsoft covers this in this article
http://msdn2.microsoft.com/en-us/library/ms778813.aspx

Saturday, June 16, 2007

And Now Another Word Form Our Administrator

I see a whole lots of posts concerning the search drop down box missing some of the defined scopes from SharedServices. I recently ran into the problem on our farm and I thought I would pass along the fix.

The best place to start is to ensure that you actually have a SearchCenter site installed on the site. Without this, there will be no place for the scoped searches to land, and the scopes will not show up.

Once you have ensured that SearchCenter is installed, then go to SharedServices and make sure that the scopes are there. With MOSS, you will usually get two defined scopes out of the box, All Sites and People. Make sure that the scopes are there and that there is data in them.

If 1 and 2 are ok then next go to the base site for your collection. Log in as the SysAdmin and click on "Site Settings - Modify All Site Settings".

From All Settings, find the Search Scopes link in the Site Collection Administration column and click on it.

Sometimes SharePoint loses the scoping information, if it has lost it for your site collection, you will see your two scopes here in the category of Unused Scopes. In order to reset your scopes correctly, click on the "New Display Group" button on the menubar.

In order for the Scopes to show up in the Search Drop Down, place "Search Dropdown" in the Title box here. Place a description in the next field. Now check both of the scope "Display" checkboxes and select a position for each (usually it is All Sites (1) and People(2). Make the default scope "All Sites" and click on "OK"

When you return to the view you will see your defined scopes in the new Category: "Display Group: Search Dropdown(2)". And when you go to your site you will see that the scopes are now part of the dropdown box.

To set up Advanced Search, create another Display Group. In this group place "Advanced Search" in the title and check on "All Sites" and make "All Sites" the default, BOOM! Advanced search shows up.

Hopes this helps some of you frustrated Sharepoint people

JMC

Tuesday, June 12, 2007

And Now A Word From Our Administrator...

Alternate Access Mappings

What is it all about?

Many installations of SharePoint will not work in Browsers if the site is not trusted. Trust works at the domain level. When you create a SharePoint site, often times you have "http:/servername/pages/default.aspx" as your url. But you want to show "http:/mymoss.domain.com/pages/default.aspx" as your url.



In order to do this you have to modify the farm's AAM settings. These settings are located on the Operations tab in Central Admin.

When you open AAM you will see a list of all the sites in the farm and the default http for each site. Now lets say you want to use "intranet.domain.com" as the url for all users connecting to the site from the intranet.



Click on the site you want to add a mapping for, then click on "Edit Public Zone URLs". In the next screen you will see 5 defined zones and any defined mappings for these zones. Usually only the default zone will have a value.

In the intranet zone text box add "http://intranet.domain.com" and click on save, then reset iis. Now any user that connects to the server from the intranet will have the new url in their browser address.



As an added benefit, you can set one of four different security levels on each AAM Zone. So lets say you do not want people from the internet to be able to create or edit items on the Farm, you set the extranet url and then in admin set all users that connect to ReadOnly.



JMC

SharePoint List Importer/Exporter: Part II

Once I had the settings from the XML file, I placed all the settings into a dictionary object for easy access. Next I had some code that checks to see if we are creating a new list or just adding to a current one. This uses the typical site/web setup.

I check the listname against the site to see if the list already exists. If the list exists, I check to see if I am creating folders and set the EnableFolderCreation property if I am.

If the list does not exist, I create it and set some properties.

SPList list = null;
if(web.Lists[inputs.ListName] != null){
list = web.Lists[inputs.ListName];
list.EnableFolderCreation = (inputs.GetItemType == ItemType.Folders inputs.GetItemType == ItemType.FoldersAndItems ? true : false);
}
else
{
Guid guid = web.Lists.Add(inputs.ListName, inputs.ListTitle.Replace("_", " "), SPListTemplateType.CustomGrid);
list = web.Lists[guid];
list.EnableVersioning = true;
list.OnQuickLaunch = true;
list.ReadSecurity = 1;
list.WriteSecurity = 2;
list.EnableFolderCreation = (inputs.GetItemType == ItemType.Folders inputs.GetItemType == ItemType.FoldersAndItems ? true : false);
}
web.AllowUnsafeUpdates = true;
list.Update();

Now I have the list object that I am going to be writing my folders and items to. I next call my import routine.

Part of this import routine if reading from the CSV files. You could alternatively use another SharePoint list or multiple lists as your import source. For the purpose of this demo I will not display the code to parse out a CSV file because are alot of sites out there that will show you haw to do that. For the import, we need to set our fields for the list. So to start the import, I read the 3 header lines from the file and then call a routine to create the fields in the list.

I placed the header data into 3 generic lists and interate through them. First I check to see if the list already contains a definition for the current field; if it does not, I add the field to the list definition by defining name, type and required.

for (int y = 0; y <= flds.Count - 1; y++) { string fld = flds[y].Replace(" ", ""); if (!list.Fields.ContainsField(flds)) { try { list.Fields.Add(fld, typs[y], reqs[y]); } catch { continue; } } } web.AllowUnsafeUpdates = true; list.Update();


I run the same process using the item field information. This ensures that all the fields are in the list.
Now the list is ready to receive the data, so the next step is to place all the data for the items into a container for quick reference. I placed the entire file into DataTable.

Next I stream in the folder CSV file. I read each line into an array called values. Then we compute the url for the list items using...
string url = string.Format("{0}/{1}", list.ParentWeb.Url, list.RootFolder.Url);

The xml settings included a node for formatting the name/title, i do this on the first line, creating essentially a computed field.
string name = String.Format("{0}-{1} {2}", values[0], values[1], values[2]).Trim();


Now we can loop through the folders definitions in the stream. Make sure you set the second value in the Add Method to SPFileSystemObjectType.Folder or else you will create items. I had placed all the field names into a list previously called fflds, so now I loop through the list and set the value for each field in the form. Finally I set the title field and save the new list folder.

while (values = csv.GetLine())
SPListItem item = list.Items.Add(url, SPFileSystemObjectType.Folder, name);
for (int i = 0; i <>

item.Update();

After the list folder has been created, now I go to the items datatable and filter it on the key field in the list folder, matching against the items. Then I loop through each item DataRow in the filtered view and add the item and set its field values. Notice that when I create the item, I set the item url to the folder url. I then save each item and loop back to get the next folder.

dt.DefaultView.RowFilter = GetFilter(item, fflds, keyStr, keys);
for (int o = 0; o < item_url =" string.Format(" folderitem =" list_items.Add(item_url," i =" 0;">

The whole process runs in under 1 minute for 3000 folders with mixed items. Even though I know there are some $$$ tools out there that can do this. For those of you without the budget this is a good place to start on your own list importer. If you would like me to send you the source for this part of the application, just send me an email. :)

In the next version I will tackle exporting complex lists into a format that is digestable by other applications.

JMC


Friday, June 8, 2007

SharePoint List Importer/Exporter: Part I

Most companys I work with aleady have some form of Intra/Inter net already in place, that they want to port over to SharePoint. These installations often take the form of a threaded discussion forum or database.



I must admit I am not entirely happy with the way lists and items are displayed in Sharepoint

But that is another topic.

I did see a need to import folders and items into a list.

So I created an import utility to read a list of items from a CSV file and store them in a data table. Then I streamed in a CSV file with a list of folders, matched on a key value and created the folder and added the items into it.

I started by exporting a view of folders to a CSV file. I included the key field and about 4 columns of data. Then I filtered the view on items and exported the items to a CSV, once again adding the key field and some columns.

I created a small xml settings file:

<ActionDefinition Run="false" Desc="EXAMPLE - Import Folders and Items into new List">
<Url>http://moss/docs/</Url>
<ListName>My List</ListName>
<ListTitle>My Folders and Items</ListTitle>
<FilePath>C:\folders.csv</FilePath>
<SubFilePath>C:\items.csv</SubFilePath>
<KeyColumn>FolderID</KeyColumn>
<LinkFormat>href={0} class='link'{1}</LinkFormat>
<RunAction>Imports</RunAction>
<CreateType>FolderItems</CreateType>
</ActionDefinition>


The file specifies the action: Import or Export; the url of the list (in this case I am creating a new list called 'My List'); the paths to the files, the keycolumn name in the files; and some formatting information.

The only formatting I had to do in the ffiles was to add 3 lines to the top of the file. These lines defined FieldName, FieldType and Required. So my folders file looked like...

FolderID,Field1,Field2,Field3,Field3
Number,Text,Text,Text,Text
true,false,false,false,false
1102,Michael,J.,Meyer,"Accounting Services"
1112,Tim,,Meyer,"Professional Services"

I created a small console application that reads from the xml settings and loads the settings

The code that processes the information I will include in Part 2....

JMC

Sunday, June 3, 2007

Profile Properties Webpart

There is something innately wrong with working on weekends. When I started working with MOSS back in July, I was fearful that perhaps it would not catch on. I don't feel that way anymore....

I do not have a whole lot of time to post, but I feel compelled to share the knowledge. My current consulting project has over exposed me to all the workings of people and profiles. At the height of the design, we were importing from three sources.

Other developers and users often ask me about the data we store for each user, and how they can view this information. Since I am not partial to giving out the server settings password, I created a small webpart that can be placed on a page and set to a user's id to display that user's information.



using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Collections.Generic;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.Office.Server;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;

namespace ProfileProperties
{
[Guid("1fe9ec91-b377-4421-bc00-7cbc39ce3061")]
public class ProfileProperties : System.Web.UI.WebControls.WebParts.WebPart
{
PeopleEditor editor = null;
private Dictionary _userProperties = new Dictionary(50);
private string _user_account_info = string.Empty;
[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
WebDisplayName("Account Info"),
WebDescription("The account of the profile to return.")]
public string AccountInfo
{
get { return _user_account_info; }
set { _user_account_info = value; }
}
public ProfileProperties()
{
this.ExportMode = WebPartExportMode.All;
}
internal void GetUserProfileProperties() {
try
{
Queue profileFlds = GetPropertyFieldList();
UserProfileManager mgr = new UserProfileManager();
UserProfile profile = mgr.GetUserProfile(_user_account_info);
foreach (string fld in profileFlds)
{
try
{
_userProperties.Add(fld, profile[fld].Value.ToString());
}
catch
{
continue;//lame I know, but its only an example
}
}
}
catch {}
}
internal Queue GetPropertyFieldList() {
Queue profileFlds = new Queue(50);
using (SPSite site = SPContext.Current.Site)
{
try
{
ServerContext context = ServerContext.GetContext(site);
UserProfileConfigManager configManager = new UserProfileConfigManager(ServerContext.Current);
Microsoft.Office.Server.UserProfiles.PropertyCollection propColl = configManager.GetProperties();
foreach (Property property in propColl)
{
profileFlds.Enqueue(property.Name);
}
return profileFlds;
}
catch
{
return null;
}
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (!string.IsNullOrEmpty(_user_account_info))
{
SPSecurity.CodeToRunElevated mf = new SPSecurity.CodeToRunElevated(GetUserProfileProperties);
SPSecurity.RunWithElevatedPrivileges(mf);
}
}
protected override void RenderContents(HtmlTextWriter writer)
{
int x = 0;
foreach (KeyValuePair prop in _userProperties) {
writer.Write(string.Format(@"{0}. {1} = {2}",++x, prop.Key,prop.Value));
}
editor.RenderControl(writer);
}
}
}



The code also utilizes the elevated privilages property for webparts that is new to MOSS.
This allows the webpart to go to the profile manager and get the field list before finding the specified profile and displaying the values.

Friday, June 1, 2007

MediaPlayer Webpart

Situation:
Customer wants to play media files in a structured way on a webpage, without popping up external viewers. The media files will be attached to pages in a list. Normally you would have to click on the attachment and launch a the viewer. This webpart plays the attached file embedded into the page.

Constraints:
Must be able to define player window size.
Must be able to handle multiple media types including .mov, .wma, .avi, .mpeg and flash.
Must be able to define control settings for each media type.

Solution:
Lets start with putting together the webpart class for this control.

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;

namespace MediaPlayer
{
public class MediaPlayer : System.Web.UI.WebControls.WebParts.WebPart
{
private string _media_string = string.Empty;
private string _media_file = string.Empty;
private string _control_width = string.Empty;
private string _control_height = string.Empty;
[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
WebDisplayName("Media File"),
WebDescription("The location of the media file.")]
public string MediaFile
{
get { return _media_file; }
set { _media_file = value; }
}
[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
WebDisplayName("Control Width"),
WebDescription("The width of the control.")]
public string ControlWidth
{
get { return _control_width; }
set { _control_width = value; }
}
[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
WebDisplayName("Control Height"),
WebDescription("The height of the control.")]
public string ControlHeight
{
get { return _control_height; }
set { _control_height = value; }
}
}
}

For lack of space, I will not include the editorpart code that I created for the control. In order to meet a constraint, I used the webpart properties to place default settings that the admin could set on the page and then placed similar settings in the editorpart so that the page creator could override the default settings.
Now that we have out class and properties defined, lets render our control. Inside our class lets add...

protected override void CreateChildControls()
{
MediaFile = GetMediaFile();
_media_string = BuildMediaString();
base.CreateChildControls();
}
protected override void RenderContents(HtmlTextWriter writer)
{
writer.Write(_media_string);
}

Now to the meat of the solution. We are going to generate our own html for this solution. In this way we can ensure exactly what is going to be writen to the browser. I would also like to mention that I am not including any of the browser variant code for other DOMs, you can figure that out :) Lets tackle embedding WMV. You can find the object/embed definitions for all media types at their associated owners sites.

internal string BuildMediaString()
{
StringBuilder sb = new StringBuilder (500);
if (string.IsNullOrEmpty(MediaFile))
{
sb.Append("No media file defined");
}
else
{
string[] tArr = MediaFile.Split((char)'.');
string type = tArr[tArr.GetUpperBound(0)];
switch (type.ToLower())
{
case "wmv":
sb.AppendFormat(
@"<object
style=""width:{0}px;height:{1}px""
classid=""CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95""
standby=""Loading Microsoft® Windows® Media Player components...""
type=""application/x-oleobject"" _ codebase=""http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=6,4,7,1112"">
<param name=""filename"" value=""{2}"">
<param name=""autoStart"" value=""true"">
<param name=""showControls"" value=""false"">
<param name=""showstatusbar"" value=""true"">
<param name=""autorewind"" value=""true"">
<param name=""showdisplay"" value=""false"">
<embed
src=""{2}""
width=""{0}""
height=""{1}""
type=""application/x-mplayer2""
autostart=""1""
showcontrols=""0""
showstatusbar=""1""
autorewind=""1""
showdisplay=""0"">
</embed>
</object>
", ControlWidth, ControlHeight, MediaFile);
break;
case("avi")...


This HTML creates an object on the page and embeds the movie at the height and width specified in the webpart settings.

We find out what media type has been requested by splitting the file name or url and using the last index as our media type. For instance if the location is "mysite/lists/Videos/Attachments/my.big.movie.wmv, our array will contain "wmv" in bucket three.

You can redefine the object / embed settings for the control to match your desired look, feel and interactivity.

JMC

Welcome to Inside Sharepoint!

Finally...

I can not express how many times I have tried to get my blog up and running. But I am sure you can agree, sometimes there just are not enough minutes in a day.

So here it is, my blog for sharing my SharePoint 2007 Portal and WSS experiences, delights and frustrations.

In the SharePoint world, I like to touch everything... front end, back end, css, javascript, controls, assemblies, services, bdc, infopath, profiles, enterprise search, templates...

I must admit though I enjoy getting dirty and cracking into the SDK whenever I get a chance. I enjoy building webparts, solutions and applications, and while MOSS comes with a tremendous amount of built in functionality, users always seem to want more.

The number one frustration vented to me is "Well SharePoint gives us 75% of what we need, but we REALLY need the other 25%". Sometimes a simple compromise can be met, other times a small extension or customization can accomplish the other 25%. And then there are times when architecting a custom solution is the only way to fix the situation.

I gotta admit these are the situations I enjoy the most. :)