Friday, February 26, 2010

Web Part architecture - what I had to fix in ChartPart

An important aspect of web part architecture is "redeploy-ability".
Redeploy-ability is when you want to use the web part as part of a site template. When you develop a web part, you need to think of redeployment - since the web part will need to be deployed with a set of pre-configured settings into a new site - the web part's properties. If those properties cannot be set as part of the site template, the web part cannot be redeployed.

Let me use ChartPart as an example.
If you don't already know, there is a project called "ChartPart" in codeplex, that shows lists as charts:
chartpart2-1.png
The web part needs to store a lot of information in its properties when it gets configured. For example, it needs to store the X axis and the Y axis columns. For this, the developer created a property called "XAxisSourceColumns", of type "List<string>". That allowed him to add column names to the list easily in the code.
The problem with this, is that this property has to be stored with the web part, and is required for the web part to work - that means if I export the web part, then I expect that when I import it again, all the settings will be there. With a property of type "List<string>", this will not happen - because the web part system does not know how to serialize a "List<string>" object into XML (a .webpart file) and doesnt export those settings. Then, if you import the web part you just exported, you lost those settings. I recommend storing the information as a simple type (defining the properties as strings for example) for serialization purposes.

A second problem, and one that you can see with a lot of Microsoft web parts as well as ChartPart (for example the Content Query Web Part) is that when a web part is connected to a list, the property stores the list's ID - a GUID, instead of the list name. This is smart - because someone may rename the list, and the ID will remain the same, so the web part will keep working. On the other hand - using an ID means that you lose "redeploy-ability" - since a list's ID gets generated randomly every time the site gets created, if your template has the web part set with a specific ID, then when you create a new site off that template, the web part will be broken - since it will not find a list with that ID in the site.
I recommend you store in the web part both the list's ID and the list's name. This way, if there is a list with the ID that was specified, the web part uses it, and therefore does not break when the list's title changes. But if there is no list with that ID (or no ID was set - which is what we will do in a template), the web part will look for a list with the name that was set - which means it will support redeploy-ability.

Finally, a problem with many web parts that allow linking to objects in other sites is that the site url property often expects a value - and does not support a "this site" option (which I like to add as a blank value). This means again that we lose "redeploy-ability" - since there is no way to configure the web part to look in this new site that was created from the template - the web part expects a link to a site...
I recommend supporting a "leave site url empty for current site" as a minimum requirement for a web part, and if you want to be real good, add support for relative URLs.

So how do we resolve this?


Usually we implement "redeploy-ability" when we start writing the web part - we create properties of type string that are not browse-able, and then create ToolParts that populate them using a good user interface - that gets the list of columns (in this example) from the user, then serializes it into a string that gets saved in the web part. It does mean a bit more effort to serialize the list and deserialize the string - but the end result is that when you export the web part you get what you expected - the serialized property gets stored in the .webpart file.

Supporting "blank" as current site url
In ChartPart to make this change was easy - I looked for all the places that SiteUrl property was used, and changed the code to check if the siteurl property is empty then to use the current site url. This is not as effective as you can make it (a function that gets a SPWeb object would have done it better) but it saved me a lot of refactoring:

string url = this.SiteUrl;
if (string.IsNullOrEmpty(url))
   url = SPContext.Current.Web.Url;
using (SPSite site = new SPSite(url)) 
{
   ...
}

Supporting list names as well as IDs
In ChartPart I added a property called ListName and then made the following modifications to all locations that referred to the list ID:

if(this.ListId != null && this.ListId != Guid.Empty)
   list = web.Lists[this.ListId];
else if(!string.IsNullOrEmpty(this.ListName))
   list = web.Lists[this.ListName];
I also changed the toolpart so that when a user picks a list from the dropdown, both the list ID and the list name gets stored.

Serializing complex type properties

In ChartPart for example, since it was already developed and I had to keep supporting existing web parts that store the information as a list, I couldnt remove the existing properties. However, what I did is add a string property for each one of the "List<T>" properties, that return the serialized value of the "List<T>" property it reflects, and when set it sets the "List<T>" property. Here is an example:

//the original property
[Personalizable(PersonalizationScope.Shared)]
public List XAxisSourceColumns
{
    get;
    set;
}
//this is for supporting exporting and importing the web part (which does not serialize List)
//we will have these as pipe seperated strings.
[Personalizable(PersonalizationScope.Shared)]
public string XAxisSourceColumnNames
{
    get
    {
        if (this.XAxisSourceColumns != null)
        {
            string ret = "";
            foreach (string xAxisColumn in this.XAxisSourceColumns)
            {
                if (!string.IsNullOrEmpty(ret))
                    ret += "|";
                ret += xAxisColumn;
            }
            return ret;
        }
        else
            return "";
    }
    set
    {
        this.XAxisSourceColumns = new List();
        if(!string.IsNullOrEmpty(value))
        {                 
            string[] values = value.Split('|');
            this.XAxisSourceColumns.AddRange(values);
        }                
            
    }
}

Monday, February 22, 2010

Making the content query web part deployable

My biggest issue with the CQWP? its not deployable when connected to a specific list in a specific site.
If you configure a CQWP web part to connect to a list in a site, the web part saves the ID (GUID) of the list - and if you want to deploy the web part as part of a feature or onet.xml so that the web part gets added every time a user creates a site, it will fail - because the ID of the list changes every time you create a new site.

My solution? override the web part and implement the following 2 functions:

public class MyBetterQueryWebPart: ContentByQueryWebPart
{
public void SetListGuid()
        {
            if (!string.IsNullOrEmpty(this.WebUrl) && 
!string.IsNullOrEmpty(this.ListName))
            {
                using (SPWeb web = SPContext.Current.Site.OpenWeb(this.WebUrl, true))
                {
                    SPList list = web.Lists[this.ListName];
                    this.ListGuid = list.ID.ToString();
 
                }
            }
 
        }
        protected override void OnLoad(EventArgs e)
        {
            if (!string.IsNullOrEmpty(this.WebUrl) && 
!string.IsNullOrEmpty(this.ListName) && 
string.IsNullOrEmpty(this.ListGuid))
            {
                SetListGuid();
            }
            base.OnLoad(e);
        }
}

This code checks if the web part is in list mode (has a list name and a web url) and is "corrupt" - doesnt have a list ID. if it finds that is the case, it dynamically loads the list ID based on the site url and the list name.

Tuesday, February 16, 2010

Help me decide on a logo for my new company - Extelligent Design

Yes, I have opened my new company and am now working for myself. I will give details soon - including releasing the web site, but I cannot do that without a decent logo. Can you help?

If you are interested in earning $300 USD, go to my logo contest and submit an entry there. It finishes on the 20th of February, so there is not much time.

You don't have to submit an entry - you can also vote on the entries other people entered and let me know what you like. To do that, go to the contest next week when the submissions has ended (after the 20th) and login to vote (you will have to register in that site - which I recommend if you have a few hours to spend looking at photoshop wonders).

At the end of next week I will announce the winner and release my brand new site - offering sharepoint consulting, training and products (I am now the official distributer of KWizCom products in Australia and New Zealand)

Monday, February 15, 2010

Error: "Failed to connect to the database server or the database name does not exist"

I have installed a new development computer at home (quad core, 12GB RAM dell studio xps) and have set up boot from VHD - to a win2008r2 machine.
I figured that setting up SQL and domain controlers on the host would be best, and hyper-v for guests (sharepoint 2007 and sharepoint 2010 beta) that would connect to the host machine's domain and sql.
However, when I tried to connect either version of sharepoint to the SQL, I got the same error:
---------------------------
SharePoint Products and Technologies Configuration Wizard
---------------------------
Failed to connect to the database server or the database name does not exist.
Ensure the database server exists, is a Sql server, and that you have the appropriate permissions to access the database server.
To diagnose the problem, review the extended error information located at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\LOGS\PSCDiagnostics_2_17_2010_8_45_30_259_232810164.log.
Please consult the SharePoint Products and Technologies Configuration Wizard help for additional information regarding database server security configuration and network access.

The solution? I turned off the firewall on the host machine...So now I am open for attacks, but atleast my virtual machines can talk to my sql on the host... So instead of leaving it off, I just followed this MSDN article that explains how to open the ports for SQL...

Wednesday, December 16, 2009

ErrorWebPart when loading a CQWP using the object model

When you are using the object model from a console or windows application to load web parts, you will notice that the CQWP (content query web part) instances all show up as "ErrorWebPart", and do not allow changing any of the web part's settings.

This is because when you load the web part, the web part code tries to access all kinds of data from it's context. Since you are loading the web part without a context (that is, from a windows application instead of a web application) the web part fails to get the context, and gets reported as an ErrorWebPart, when infact it works well if you look at it on the web interface.

I have yet to find a solution for this, but one workaround that works is to move your code to a web page, in the "_layouts" folder. This allows the code to run withing the site's context, and so accessing the web part does not cause it to report ErrorWebPart.

Update: It has been brought to my attention by a reader that Sandeep has a workaround for this:http://sharepoint-insight.blogspot.com/2008/10/sharepoint-all-webparts-appear-as.html

Tuesday, November 24, 2009

SharePoint 2010 - virtual vs non virtual

This has been a very bad week - since the Beta of SharePoint 2010 came out I have been trying to install it and set up a demo environment for developers on my laptop - without much success. Since 2010 requires 64 bit, this ruled out using VPC, and since I don't like using 2008 as the host on my laptop, this meant I had two choices: virtualize with a non-MS solution that allows 64bit clients (virtualbox by sun or vmware) or install sharepoint directly on my windows 7 host - which means I would have Beta software running on my production laptop.

I chose to try virtualbox - and failed twice. First I installed windows 2008r2, and during my presentation to the user group last week it was a disaster - the site kept crashing with unknown error, nothing in the logs and nothing working.

Then I installed a fresh install of windows 2008 with all service packs in the virtual environment - and again installed sharepoint. Again - same symptoms - the site crashes and works SLOWLY.

Finally today I gave up and installed the entire 2010 beta system on my laptop itself - on the windows 7. Apart from the fact that I had to download and install the prerequisites manually (on the server it did it automatically for me) and apart from the fact that I didn't follow the instructions very well (I used 7-zip to extract the .exe file so I can change the config.xml file - which put all files in the one folder - losing the hierarchy. it turns out this gives a "wrong language" error (I forget the exact syntax)) the installation went quickly and easily - so now I have a stand alone windows 7 with sharepoint 2010 and office 2010 and VS2010.

I will keep updating when I have time to play with all of the above...

Modified date stopped working...

So here is the scenario - I had a piece of code in a feature that changed the title of the built-in site column "Date Modified" to...well..."Date Modified". As you can see, the code looks a bit useless, but someone thought it would be useful, so there it was. Next thing I know, the date modified field stopped working in out of the box document libraries. You would edit a document or its properties and the modification date would be the same as the creation date. This would even show in the version history...

Solution - I had to write a small piece of code that would set the SchemaXml property of the site column to the default. It turns out that if you change the field using code (like SPField.Title="Date Modified"; SPField.Update()) then sharepoint removes some very important tags from the schemaxml property of the field - which causes it to break. To fix I just copied over the SchemaXml property from a site column in a brand new site that was not damaged.

Code sample:

try

{

using (SPSite site = new SPSite(txtSite.Text))

{

using (SPWeb web = site.OpenWeb())

{

SPField field = web.Fields.GetField(txtField.Text);

if (field != null)

{

field.SchemaXml = txtSchema.Text;

field.Update(chkUpdateLists.Checked);

MessageBox.Show("Operation completed successfuly");

}

else

{

MessageBox.Show("Field '" + txtField.Text + "' could not be found in the site columns in" + Environment.NewLine+ "'" + txtSite.Text + "'");

}

}

} } catch (Exception ex)

{MessageBox.Show("An error has occured : " + Environment.NewLine + ex.Message);}

Sample schema: <Field ID="{28cf69c5-fa48-462a-b5cd-27b6f9d2bd5f}" ColName="tp_Modified" RowOrdinal="0" ReadOnly="TRUE" Type="DateTime" Name="Modified" DisplayName="Date Modified" StorageTZ="TRUE" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="Modified" FromBaseType="TRUE"/>

Thursday, November 12, 2009

Event handlers in SharePoint 2010

This is too cool...so many more options - take a look at the visual studio 2010 wizard for creating an event handler - it is more than just list items...

I really like the web events - these will be very useful and will allow creating complex solutions without site definitions.

As soon as the beta is out I will write some samples!

Thursday, October 22, 2009

Closing thoughts from the SharePoint conference

I am sitting in the last session, and I have to admit- I am a little bit afraid of SharePoint 2010. There is just so much more, that it makes the step from SharePoint 2003 to 2007 seem small in comparison. I think we should thank the SharePoint product team for an enormous effort to get all this new stuff in - and give us some very complex stuff to work with.

I am looking forward to doing search solutions, writing visual web parts, developing better event handlers (post synchronous events!), designing in sharepoint designer and then moving the design to visual studio and more and more and more.

If you get access to the presentation videos - make sure you listen to Mike Ammerlaan's presentation titled "Architecture guidance for building applications in SharePoint 2010" - not sure how much of that will be reviewed during the beta period (starting next month) but this was THE best and most important presentation in the conference - giving us thumb rules of what to use when, and some key pointers to making decisions on what to use in different development scenarios. Thanks Mike!

Finally, there are things I am dissapointed with - like a continued lack of support for custom fields in office, and no exciting new field controls. But the good overweighs the bad. I will have a think about what I have seen in the next two weeks, and then start blogging again - from TechEd Europe.

For now - I am going to see the grand canyon, visit Munich, and have a week's vacation in Israel with my family. Be patient, and more posts will come in tow to three weeks. If you are coming to TechED and want to chat - see you in Berlin in the Ask The Experts booth!

Wednesday, October 21, 2009

What I like and dont like about the new BDC (the BCS)

To get one thing clear - when BDC came in MOSS2007 I was very excited about it. I really thought it was great...until I had to do a real life project with it.

No, I am not talking about the pains I went through to create an application definition (this was before the BDC metaman or the tool that Microsoft released a bit later) - I am talking about how the BDC integration to sharepoint was so limited.

Every single customer who had a problem that I wanted to solve using BDC had the exact same requirements that I couldnt give them using BDC:

  1. We want it to work in office
  2. We want it to be a multiple item selection

There is no way in MOSS2007 to achieve both in BDC. You can develop a custom field type that will allow multiple selections from BDC information, but you cannot use a custom field type in Office. And the built in BDC column type only allows single selection.

That makes me sad.

In 2010, the "BCS" is much improved. It makes me even more excited about what I can do with it. It has "external lists" - which are fancy web parts that make the BDC data appear to be as if it is in a sharepoint list- including filtering, adding items, editing items and so on (not full functionality of a list though). There are two designers for the applications - no more XML! just use sharepoint designer or visual studio and create application definitions easily. Heck, in VS2010 you can create the connector to the LOB system in the same project as the application definition model - and refactor stuff later. This is EXCITING and GREAT stuff. Not to mention the object model, the (much) better support for item level security and so on.

But it still does not solve the problem I described above - the BCS column type is still a single lookup.

I was hoping that Microsoft would introduce better support for custom field types in Office 2010, but that is not happening either, so there is no oppertunity for me to develop a custom code to do that.

This makes me even sadder.

Can everyone please join me in asking Microsoft to

  1. Release a better BCS field type before RTM of as part of SP1 and make BCS perfect
  2. Redesign how custom fields are supported in office applications for SharePoint 15

Meanwhile, I will enjoy the cool new features.