Thursday, October 28, 2010

SharePoint Explorer View, Windows 7, and the WebDAV Redirecter

Nothing is more frustrating than having something work on one computer, then not working on another... Some of this is to be expected, especially when upgrading Operating Systems, but with server based products, you expect most clients to act the same way. They are, after all, connecting to a program on another computer.

My frustration was the Explorer View in SharePoint. This view is a great tool for uploading lots and lots of files to a document library. It is also a great tool to show users that are used to having their documents on a file share. This view gives them a nice warm fuzzy that things aren't changing that much for them.

What I found was that in Windows XP IE 8, I could open the Explorer View just fine, however when I walked over to my Windows 7 IE 8 box, no dice. What the...

What I found was this. Microsoft changed how they handled various functions that are allowed to access to the Operating System. WebDAV being just one such function. It is important to note that SharePoint has its own WebDAV functions separate from IIS' WebDAV. More on that later.
In Windows 7 the WebDAV Redirecter Service must be turned on in order for SharePoint WebDAV to have access to Windows Explorer. If the service is not on, you either get an error saying that your browser doesn't support Explorer View, or, most frustrating of all, nothing happens. Flip on the Web Client Service, and you should be good to go.
Yeah, I realize that the service name is Web Client Service, and that the process is called WebDAV Redirecter. I don't know why. Microsoft is funny like that.

So, say you are setting up your server for a new SharePoint implementation, and you want to use the Explorer View for your document libraries. What do you do? Ignore WebDAV. Seriously. On both IIS 6 and IIS 7/7.5 servers, do not enable or install the WebDAV features. Windows 2003 will work regardless of it being enabled, but installing it on Windows Server 2008 will break Explorer View. So it is just good practice to leave WebDAV off of the box entirely. Why enable something that has to be patched, updated, and monitored if you are not using it?

Friday, October 22, 2010

Presence in SPGridView MOSS 2007

A company I work for will be incorporating Windows Communicator with their VOIP and IM system.
We also have a web based Corporate Directory application.
Wouldn't it be nice, someone thought, if we could put the Windows Communicator Presence Indicator in to our Corporate Directory?
The Corporate Directory is currently a SharePoint Data View Web part. Adding Presence would require custom development.
Off I go.

The first issue I ran in to was that there is some debate on how to get Presence information in to SharePoint (MOSS) 2007, some blogs say to use the IMNRC java script, while others say to use the imnmark image with a IMNImageOnClick java script event.
Confused? Me too. The problem is that Microsoft left the old way to do Presence in to the new version of SharePoint, so what would work in 2003:


<span><img border=\"0\" height=\"12\" src=\"/_layouts/images/imnhdr.gif\" onload=\"IMNRC('"you@domain.com"')\" ShowOfflinePawn=\"1\" alt=\"\" id=\"user_presence_icon\"/></span>


will work in 2007, but the way Microsoft does Presence in 2007 is with the anchor and image tags:


<a href="javascript:" onclick="IMNImageOnClick();return false;" class="ms-imnlink"><img title="" alt="No presence information" name="imnmark" border="0" valign="middle" height="12" width="12" src="/_layouts/images/blank.gif" sip="you@domain.com" id="imn1,type=sip" ></a></span>


Great! It works for just one user, with their email address hard coded. The problem is that I have lots of users, and everything is stored in a database.
Easy!, you say, Just use a GridView and put that stuff in there!! Sounds good, but SharePoint always has a way of messing with us. The SharePoint version of a GridView is the SPGridView. It is just like a GridView, only harder. In SharePoint you do nearly everything in the code. It sucks. Personally, I like the ability to use the Design tap of Visual Studio it lets me know what I have going. So, I tend to use User Controls so that I can see what is going on. Only that does not really apply here.
Anyway, typically what you would do in ASP.NET programming would be to drop a GridView on a page and set up your fields. Because we are using some HTML you will need to create some template fields. Generally looks like this:


<asp:GridView ID="GridView1" runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>


No problem! Except that we are in SharePoint and we have to do all of that in code...

This is where it really gets sticky. You will notice that the Template Field is called as a control. That means that it is a part separate from the GridView. That makes your life in SharePoint difficult.

As Oscar Medina details in his awesome blog post SPGridView - Using a custom TemplateField to add a Checkbox Column you can declare a Template Field inside your SPGridView, but you will need to create a class for your Item Templates.

A class that inherits from ITemplate, and you will need to override the InstantiateIn method in order to stick your stuff in.

During my very frustrating trial and error period, I found that the best way to get the Presence stuff to work is to use the new 2007 method, but convert all of that HTML in to ASP.NET controls. The ITemplate object likes those better. So what I ended up doing was creating two Literal Controls for the span tags, a Hyperlink Control for the anchor, and an Image control for the image. I used the Attributes.Add for all of the extra stuff in the anchor and the image tags that is not supported in the normal control.
So your code from above now becomes:


LiteralControl lc = new LiteralControl();
lc.ID = "lcSpan1";
lc.Text = "";


HyperLink hypLink = new HyperLink();
hypLink.Attributes.Add("onclick", "IMNImageOnClick();return false;");
hypLink.CssClass = "ms-imnlink";
hypLink.NavigateUrl = "javascript:";


System.Web.UI.WebControls.Image imgPresence = new Image();
imgPresence.Attributes.Add("name", "imnmark");
imgPresence.Attributes.Add("id", "imn1,type=sip");
imgPresence.ImageUrl = "/_layouts/images/blank.gif";
imgPresence.Attributes.Add("sip", "you@domain.com");
imgPresence.ID = "imgPresence";


LiteralControl lc1 = new LiteralControl();
lc1.ID = "lcSpan2";
lc1.Text = "
";


Sweet huh? Again, it works great for hard coding a single user to a Presence indicator. We don't want that. We want something we can stick in to a SPGridView to display a whole bunch of people. Here we go.

First you need to set up your data source. I like to use DataTables.


DataTable DT = new DataTable("Data");
DT.Columns.Add("Name");
DT.Columns.Add("Position");
DT.Columns.Add("Presence");
DataRow row = DT.NewRow();
row["Name"] = "Natto Ninja";
row["Position"] = "Goofball";
row["Presence"] = "nattoNinja@domain.com";
DT.Rows.Add(row);

row = DT.NewRow();
row["Name"] = "Megan Fox";
row["Position"] = "Megga Hottie";
row["Presence"] = "iamhot@domain.com";
DT.Rows.Add(row);

row = DT.NewRow();
row["Name"] = "Al Sharpton";
row["Position"] = "King of the Goofballs";
row["Presence"] = "KOG@domain.com";
DT.Rows.Add(row);


DataTables are fun. Now we need to create our SPGridView. There are a couple of different ways to do this, if you are using a control, you can use Ted Pattison's approach or you can just create it in the code. Calling it in the code makes it tougher for you to place on the page, but Pattison's approach requires you to create a code behind (not a code beside, very important distinction. If you don't know the difference you need to read Pattison's article). At any rate you have to create the grid.


SPGridView grdSPGrid = new SPGridView();
//Createyourfields
SPBoundFieldfldName=newSPBoundField();
fldName.HeaderText="Name";
fldName.DataField="Name";
grdSPGrid.Columns.Add(fldName);

SPBoundFieldfldPosition=newSPBoundField();
fldPosition.HeaderText="Position";
fldPosition.DataField="Position";
grdSPGrid.Columns.Add(fldPosition);

TemplateFieldfldPresence=newTemplateField();
fldPresence.HeaderText="Presence";

//Call your class that creates your Item Template
fldPresence.ItemTemplate=newGridViewTemplate("Presence");

grdSPGrid.Columns.Add(fldPresence);

//Call the method that inserts your row information in to your image control
grdSPGrid.RowDataBound+=newGridViewRowEventHandler(grdSPGrid_RowDataBound);

//Bind the the DataTable to the SPGridView
grdSPGrid.DataSource=DT.DefaultView;
grdSPGrid.DataBind();


You will notice the place where I call a class to get my Item Template going. It is in that class where I create my objects that will become my Presence indicator.
Here is that class:


public class GridViewTemplate : ITemplate
{

private string columnName;

public GridViewTemplate(string colname)
{

columnName = colname;
}

public void InstantiateIn(System.Web.UI.Control container)
{

LiteralControl lc = new LiteralControl();
lc.ID = "lcSpan1";
lc.Text = "";
container.Controls.Add(lc);

HyperLink hypLink = new HyperLink();
hypLink.Attributes.Add("onclick", "IMNImageOnClick();return false;");
hypLink.CssClass = "ms-imnlink";
hypLink.NavigateUrl = "javascript:";
container.Controls.Add(hypLink);

System.Web.UI.WebControls.Image imgPresence = new Image();
imgPresence.Attributes.Add("name", "imnmark");
imgPresence.Attributes.Add("id", "imn1,type=sip");
imgPresence.ImageUrl = "/_layouts/images/blank.gif";
imgPresence.ID = "imgPresence";
container.Controls.Add(imgPresence);

LiteralControl lc1 = new LiteralControl();
lc1.ID = "lcSpan2";
lc1.Text = "
";
container.Controls.Add(lc1);


}

}


That builds our template field. Now for the last little piece. There has to be a way to insert the data from the DataTable in to a variable that we can set in to the image control.
To do that, we need to go in during the data binding of the SPGridView. Which is why I have this line in the code that creates the grid:


grdSPGrid.RowDataBound+=newGridViewRowEventHandler(grdSPGrid_RowDataBound);


That guy calls this method in the same class:


void grdSPGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
string row = "";
//Find the row with the data
DataRowView rowView = (DataRowView)e.Row.DataItem;
row = rowView["Presence"].ToString();
//Create a new image control and cast it as the control in the grid
System.Web.UI.WebControls.Image imgPresence =
(System.Web.UI.WebControls.Image)e.Row.FindControl("imgPresence");
imgPresence.Attributes.Add("sip", row);

}


}


That is all there is to it! It took me nearly a week to figure all of this stuff out. It was a lot of fun finding the right events, and creating the proper event handlers and stuff.

In case you are confused, the code that creates the SPGridView and the code that does the special binding all should be in the same class. The class that creates the ItemTemplate can be in a separate class file, or just as another class in the same class file as your other class.
Hope this helps some people.

Many thanks to Oscar Medina and Ted Pattison. Their posts really helped be out, and I surely would not have gotten my grid to work without them.

Thursday, October 7, 2010

SharePoint Template Gotchas

I am having to do a lot of simple web part movement and site design stuff lately and I ran across some issues that threw me...

In the past, I have created a site and configured the basic web parts that I wanted set up the primary pages generically and configured everything the way I wanted to see it anytime I created a new sub site. I would then go to the Site Settings and to Look and Feel then save the site as a template. Anytime I wanted my generic site look, I would simply create a new subsite with this template. Easy peasy.

This time I was working on a Publishing Site that would need several sub sites that would all have the same basic properties, lists, and web part views. I created the first site, clicked on up the Site Settings, looked over at Look and Feel and... Nothing. No Save Site as a Template link. What the...?? I stated going through the site features and even in to Central Admin to see if I had all of the Farm Features active that I needed to be active. No Dice.

It turns out that Publishing Sites can not be saved as templates. Bummer.

SharePoint being SharePoint, many times you can do things you are not supposed to do if you know how to use the forms and other features, I decided to poke around to see if I could get things to work the way I wanted to. The first thing I found was that if I deactivated the Publishing features, I could save the site as a template, create a new site, then activate the Publishing features at that time... This would do what I wanted... kind of. I wanted a better solution, so I tried a trick that I learned to do when I had a number of web parts that I wanted to delete from a page, to grab the actual template save application page URL and append that on to my publishing site URL to see if I could get it to save the template as I wanted it.

The URL of the save as a template application page is: _layouts/savetmpl.aspx. So my site URL: http://MOSS2007/TemplateSite became http://MOSS2007/TemplateSite/_layouts/savetmpl.aspx
To my wonder and surprise the application page popped up and I was able to save the site as a template. I created a new site using this template, and it worked like a champ.
Turns out my buddy Shane Young already had this figured out way back when. Go figure...