Friday, April 27, 2012

Changing SharePoint Site Definition to Original Team Site using stsadm.exe

Imagine you have a custom Site Definition in your MOSS 2007 SharePoint installation that is basically the same as the original Team Site Site Definition. In the end, your custom Site Definition ended up not being needed at all because it was so much like the original Team Site Site Definition. Now every time you upgrade SharePoint or stand up another copy of your SharePoint you have to upgrade and install these custom site definitions. This may also unnecessarily complicate any migration to SharePoint Online (part of Office 365). So, you decide you want to change the Site Definition that these sites use, to now use the default Team Site Site Definition instead of your custom one. One of the big requirements is that the we have a full fidelity copy of the site. By that I mean we don’t lose version history of files in document libraries and all site content is preserved. The question is how do you do this?

I guess you can use the SharePoint API to do this with SPExport and SPImport. However this is not what this post is about. If you want more information on doing this with code, try here and here for a start.

We will use stsadm.exe to do an export / import of a site (and all its sub-sites). It is very simple, and hopefully bug free. I got the idea from here, but it was a bit confusing to me and all the attributes didn’t make sense to me, so I am recording my variation of the technique. Export will generate a bunch of files. In particular there are two very important files the Manifest.xml and the Requirements.xml files. We can modify them slightly and then use Import to re-create the sites. If you will be putting them in the same location again, you will want to delete the sites from SharePoint before you import your modified sites. If you don’t you will get an error.

The Solution

Go to your SharePoint server and bring up a command prompt. Change directories to

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN

Here you will find stsadm.exe. This is the main tool we need besides a text editor.

stsadm -o export -url http://yourhostHere/SiteDirectory/YourSiteHere -filename c:\temp\YourSiteHere -includeusersecurity -versions 4 –nofilecompression

Locate the Manifest.xml file and open it in your favorite text editor. Close to the top you will find a <web …> tag. It has a bunch of attribute. Change the all instances of WebTemplate attribute to STS. STS is the Team Site Site Definition name. NOTE: There should be once instance for each site or sub-site.

In the same directory locate and edit the Requirements.xml file. Look for the tag with attribute Type=”WebTemplate” and change the Id attribute in that tag to be Id=”STS#1”

At first I didn’t see any need to change the SetupPath, so I didn’t. Then I found out that it needs to be changed in order for the Save site as template under Site Settings to create a valid template. It will create the template ok, but then when you try to use that template to re-create the site it will give you an error and kind of be there, but not accessible; a broken state really. To fix this make sure you change the setuppath. In particular, you will need to change where it references your template name to STS. After you are done, the setuppath should be something like SiteTemplates\STS\default.aspx for Team Site or SiteTemplates\STS\defaultdws.aspx if your site definition is based on a Document Work Space.

As it turns out the xml files are documented here and is a supported API. So, I guess it is not surprise what we are doing works fine. Here is the docs for the Manifest.xml and the Requirements.xml file.

Assuming you are importing the changes back into the same place you exported them from you will need to delete the old site and then import the new one. If you don’t delete the old one it will complain that the site definition is different if I remember correctly. Using the UI is pretty easy way to do this because it is recursive whereas the command line is not recursive and you will need to delete the sites from leaf nodes first then work your way up the upside down tree of sub-sites.

stsadm -o import -url http://yourhostHere/SiteDirectory/YourSiteHere -filename c:\temp\YourSiteHere -includeusersecurity -updateversions 2 –nofilecompression

The last step is to go to the site in your web browser and then go to Site Settings | Site Master Page Settings page. You will likely get an error that says the Master page is not valid. Just click the OK button to use the default or change the radio buttons if you like.

This works fairly well. What it doesn’t do is keep the alerts and workflows. As far as I know there is no way to keep the alerts other than to recreate them or write a utility to export / import them.

Additional Resources

Archive and retrieve content by using stsadm import and export operations

Wednesday, April 18, 2012

Adding Custom Property to an Entity in Entity Framework

Let’s assume we have the following scenario.  We have a Person entity in the Entity Framework Model and it a relationship to a Pet entity. Assume the Pet entity has a property called PetName that we want to show on the Person detail, edit, grids, etc. The easiest way is to add a property to the Person entity called PetName. The trick is how to populate it and how to save any changes that users make to it. You can use this same technique (described below) regardless of whether the data you are pulling into the Person table is in your model, entity, etc.

Yes, I understand this example is not really a great example. The model really isn’t that great, but I hope you get the idea.

The first thing to know is that for any entity in your Entity Framework Model you can extend that object by creating a partial class that is in that entities same namespace and classname. Here is our new partial class.

namespace
{
public partial class Person
{
private string _PetName;
public string PetName
{
get
{
string val = sting.Empty;
if (!string.IsNullOrEmpty(_PetName)) val = _PetName;
else
{
if (this.Pet != null)
{
val = this.Pet.PetName;
}
_PetName = val;
}
return val;
}

set { _PetName = value; }
}

 

Notice that we populate the PetName property in a lazy load fashion. Meaning, we don’t go get the data until it is requested. If you use Include(“Pet”) when you get the Person object then the Pet object will be in memory already and not a separate round-trip to the database for each person that is accessed. This query would typically be in your Domain Service and might be called something like GetPersons() or GetPeople() if you renamed it.

This great for displaying information. Note that you don’t need the Set portion of the PetName property if you are just displaying the information and not editing it.

Adding Editing Functionality


To handle the editing we need to go to the Domain Service. Here you should have a UpdatePerson() and InsertPerson() methods. In each of these methods we want to do something to the Pet entity or even call a stored procedure, etc. In our case the the Pet entity is what we want to update so I’ll show you how to do that. Be sure to do the update AFTER the AttachAsModified call otherwise the Load() method call will fail because the currentPerson is not in the ObjectContext.

public void Person(Person currentPerson)
{
this.ObjectContext.People.AttachAsModified(currentPerson, this.ChangeSet.GetOriginal(currentPerson));
if (!currentPerson.PetReference.IsLoaded) currentPerson.PetReference.Load();
if (currentPerson.Pet != null)
{
currentPerson.Pet.PetName = currentPerson.PetName;
}
}

 

Limitations

This is actually pretty easy as you can see. One thing to note about adding properties in general is that you will get a runtime error if you try to use property we added in a Linq To Entities query. This means that filters and sorting by clicking on column headers on the GridView generally won’t work without some extra works that changes the property to the path it actually maps to. In this case it is possible, but in other cases such as stored procs being called, this mapping would not be possible.