Monday, 14 January 2008

MOSS - Getting a all items within a Sharepoint List recursively

When you query a list in MOSS, you only get the top level folders by default. If you have a folder hierarchy within your Sharepoint 2007 list, you will not see the documents underneath the top level of your list. It is important to note that you shouldn't go through each folder in this top level and then querying each folder for a list again. Instead, you should just use the Scope="Recursive" setting
(Add to your queryoptions Xml fragment).

For more info, see the following:

http://sqlblogcasts.com/blogs/drjohn/archive/2007/11/02/Getting-a-list-of-files-from-a-moss-document-library-using-a-SharePoint-web-service.aspx

There is also an element you can add to the the Xml fragment to to restrict the results to one folder - the "Folders" element. For more details, see:

http://msdn2.microsoft.com/en-us/library/lists.lists.getlistitems.aspx

For example, to recursively get all documents in folder a folder named "Folder 10", your Xml fragment would be

"<queryoptions><viewattributes scope="\"><folder>Folder 10</folder></queryoptions>"

Another article I found useful on recursively enumerating folder contents is at:
http://blog.krichie.com/2007/01/30/traversing-sharepoint-list-folder-hierarchies/

Thursday, 10 January 2008

Subversion Branching And Merging (using TortoiseSVN)

A sad fact of life is that you sometimes have to do things you don't agree with. For my last 2 clients (one for SSW - Pfizer and one for Oakton - Lend Lease), I have had to use Subversion (SVN) rather than Microsoft's championed Team Foundation System (TFS). One of the features lacking in subversion is what TFS calls shelving.

You've got to use what you've got (SVN) to your best advantage but Andre Gallo, one of my colleagues from Lend Lease, has been struggling with it. In particular he's been having some serious issues with getting the current HEAD version from the trunk into his revision - and from the revision to the main trunk of our source code repository. Here are some of the discoveries I made this afternoon when trying to get branching and merging working smoothly for Andre, myself and the rest of our team:

We received some documentation from one of my colleagues from Oakton (Alexey Greyze) to assist us getting up and running with a good branching solution using subversion. One thing from his documentation that I did not agree with is to have a branch per JIRA ticket (aka one branch per development task). I regard this as excessive and have decided on a much simpler file system/structure for our branching. One thing that is also not clear from the SVN documentation is the fact that the merge tool is used to replay changes on your current working copy - NOT to merge from one branch to the other (e.g. from the branch to the trunk) directly.

As is suggested by many articles (e.g. http://devnulled.com/content/2006/10/guide-and-best-practices-for-subversion-branching/), excessive merging between branches is complicated to work with and should be avoided. You should try and have as many developers working on the trunk (or a single branch e.g. the dev branch) as possible. This helps to avoid massive merging sessions after 2 weeks of changes that can cause problems. As one article put it "Branches are for the minority." . The only reason to use branches per developer is if you have a team where communication is difficult (which doesn't apply in our case) and it is hard to get them to fix up issues if they check bad code into the trunk/central branch.

We will keep it simple and just have the following structure:
  1. /Dev folder
  2. /Trunk folder
  3. /Build folder (used by Cruise Control for daily builds)

One of the articles I came accross also helped us to understand how merging works with Branching can be found here:
http://nedbatchelder.com/text/quicksvnbranch.html

A CRITICAL point of understanding about the Tortoise SVN Merge dialog (and the SNV merge process in general) is that the "FROM" and "TO" sections of the dialog are not that you are merging between 2 different locations. Instead, the "FROM" and "TO" sections indicate that you are REPLAYING each change in a single location (e.g. http://subversion.apac.lendlease.com/svn/IT/Global/LendLease.IM/dev/davidklein/LPP-117) from revision 100 to revision HEAD on the folder that you right clicked on for the merge using SVN. If I right-clicked on the trunk folder and clicked merge, this would replay all changes logged on http://subversion.apac.lendlease.com/svn/IT/Global/LendLease.IM/dev/davidklein/LPP-117 betwen 100 and HEAD on my working copy of the trunk.

Note that this dialog is basically saying it is going to replay all the changes made between 100 and my head revision on my current working folder (ie the one I right clicked on and clicked "Merge")

When you are merging back to the trunk, you may also run into the following error:

Error: Unable to find repository location for 'http://subversion.apac.lendlease.com/svn/IT/Global/LendLease.MasterDataManager/trunk' in revision 32797

To fix this, you just need to change the revision number to the one after you did the branch. ie 32798 in this example.

Wednesday, 9 January 2008

PRB: You get the error "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control"

I ran into this issue again today in an ASP.NET 3.5 project (when controls auto postback inside a formview/detailsview). After the postback, you get the error "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control".

According to this article:
http://www.webswapp.com/codesamples/aspnet20/dependentlists/default.aspx

"When you have a dropdownlist that is dependent on another within a FormView server control, it becomes populated on SelectedIndexChanged event of the first dropdown list. However, in this case the template was not rebound to the data, only the dropdown list. The FormView was reconstructed from the saved ViewState. This causes the error message: "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control".
The solution is to remove the 2-way databinding from the cascading dropdownlists and replace it with customized code during processing the ItemUpdating event. "

For more details about the solution, see http://www.webswapp.com/codesamples/aspnet20/dependentlists/default.aspx

Tuesday, 8 January 2008

Integrating ASP.NET applications with Sharepoint

This article provides a good rundown on the various ways that you can get ASP.NET applications and MOSS 2007 talking to each other.
http://blogs.msdn.com/sharepoint/archive/2007/10/26/integrating-sharepoint-with-other-portals-and-applications.aspx

One of my requirements for the property pipeline application here at Lend Lease is to provide a photo gallery that reads and writes to Sharepoint from an ASP.NET 3.5 app. Of course, the simplest way to do this would be to host the list in an iframe if possible...

Thursday, 3 January 2008

You receive an error "No keys specified for the current object data type" when using an ObjectContainerDatasource in ASP.NET 3.5

Just in case anyone encounters the same issue and can't work it out - I had this error crop up when doing 2-way databinding with an object container datasource (OCDS).

"No keys specified for the current object data type"

When I made my programmatic call to FormView.UpdateItem(false), I was constantly getting this error.

Turns out I was rebinding the FormView outside a postback handler. The fix was to move the
FormView.DataBind() to inside a Page.IsPostback check.

There can also be an issue with bound controls "losing" the DataKeyNames property. You can fix this issue by specifying the DataKeyNames in code - e.g. addToAssetFormView.DataKeyNames = new string[] {"ObjectId"}; before the control is bound (or rebinding with addToAssetFormView.DataBind();)

This problem can occur with any databound controls like gridviews, not just formviews.

Wednesday, 2 January 2008

ASP.NET 2.0 + Profiles

I had some questions today about the Profile Provider and why it was not working in a particular setup. It was a problem with their settings in the web.config. Here is one of the clearer explanations of how to set up the config.
http://www.theserverside.net/tt/articles/showarticle.tss?id=CreatingProfileProvider

This MSDN article is also handy: http://quickstarts.asp.net/QuickStartv20/aspnet/doc/profile/default.aspx

All you have to do is add settings like below to the web.config:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<profile>
<properties>
<add name="BirthDate" type="DateTime"/>
<add name="FavoriteNumber" type="int"/>
<add name="Comment" type="string"/>
<add name="FavoriteColor" type="string" defaultValue="Blue"/>
<add name="FavoriteAlbums"
type="System.Collections.Specialized.StringCollection"
serializeAs="Xml"/>
</properties>
</profile>
</system.web>
</configuration>


Then use HttpContext.Current.Profile like this (no additional classes required):



// BirthDate is DateTime
textDob.Text = Profile.BirthDate.ToShortDateString();

// FavoriteNumber is Int32
textFavNumber.Text = Profile.FavoriteNumber.ToString();

// Comment is String
textComment.Text = Profile.Comment;

// FavoriteColor is String
dropDownFavColor.SelectedValue = Profile.FavoriteColor;

// FavoriteAlbums is StringCollection
foreach (string album in Profile.FavoriteAlbums)
{
listBoxFavAlbums.Items.FindByText(album).Selected = true;
}



The real question is: where does the strongly typed Profile come from? The ASP.NET compiler parses web.config to uncover the profile schema defined inside, and then code-generates a class in the Temporary ASP.NET Files directory. This is the class that is created when you add these elements to the web.config:



public class ProfileCommon : System.Web.Profile.ProfileBase {

public virtual short Age
{
get
{
return ((short)(this.GetPropertyValue("Age")));
}
set
{
this.SetPropertyValue("Age", value);
}
}

// other properties ...
}



See http://www.odetocode.com/Articles/440.aspx for a sample of this code and a more detailed walkthrough of how the httpmodules and proxy classes created by .NET are created and used.

Sunday, 23 December 2007

IINet vs Exetel - is it worth going naked? Maybe not...

I currently use the ISP IInet using the 10GB Off Peak/10GB On Peak (ADSL 2+ light) package. However, I have heard a lot of buzz about the so called 'naked' plans available through IInet and other ISPs like Exetel. Up till now, I grudgingly needed a phone line just for ADSL. To me that was $30 bucks down the drain.... until now. The naked plans don't require line rental at all!

On the face of it, I prefer the IInet deal over Exetel for my situation because:
  1. IInet have been a great provider so far and there are some rumours about Exetel having a less than stellar approach to customer service.
  2. I already have a Belkin router (F1PI241ENau - http://catalog.belkin.com/IWCatProductPage.process?Product_Id=247190) and have heard that they don't play well with the Exetel service. I don't want to fork out $200 for a new modem that works with the Exetel naked service.
  3. You get an incoming number as part of the IItalk service. Apparently you have to pay $10 extra to add this functionality with Exetel.
  4. Even though uploads are counted as part of your bandwidth allowance (Exetel doesn't charge for this), I don't upload that much ( about 1GB per month ).
  5. I don't really care about having a static IP (this is what Exetel provides).
  6. P2P are shaped (according to official announcements) on the Exetel network (see http://whirlpool.net.au/article.cfm?id=1734&show=replies )
  7. No excess usage charges (just shaped) - wheras Exetel charges $3 per GB.

However, after considering some of the drawbacks (e.g. measured uploads including voice), I am staying put for now to see where the new naked DSL market heads.

Thursday, 20 December 2007

Golden Oldie - ASP.NET multiline doesn't enforce MaxLength property

This one is a Golden Oldie and is a problem with the Textarea HTML control (not specifically a problem with ASP.NET). Technically, the ASP.NET controls should not even provide the MaxLength property when TextMode is set to MultiLine....

But there are ways around this issue using javascript and/or inherited controls:


using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace LendLease.MRP.Web.UI.WebControls
{
//Custom TextBox - used to fix issue with the Maxlength not working in controls
//modified version of http://www.codeproject.com/KB/aspnet/Extended_ASPNET_TextBox.aspx
public class TextBox : System.Web.UI.WebControls.TextBox
{

protected override void OnPreRender(EventArgs e)
{
if (MaxLength > 0 && this.TextMode == TextBoxMode.MultiLine)
{
if (!Page.ClientScript.IsClientScriptIncludeRegistered("TextArea"))
{
Page.ClientScript.RegisterClientScriptInclude("TextArea", ResolveUrl("~/Include/TextBoxMaximumLength.js"));
}
this.Attributes.Add("onkeyup", "LimitInput(this)");
this.Attributes.Add("onbeforepaste", "doBeforePaste(this)");
this.Attributes.Add("onpaste", "doPaste(this)");
this.Attributes.Add("onmousemove", "LimitInput(this)");
this.Attributes.Add("maxLength", this.MaxLength.ToString());
}
base.OnPreRender(e);
}
}
}


JAVASCRIPT:


function doBeforePaste(control){
maxLength = control.attributes["maxLength"].value;
if(maxLength)
{
event.returnValue = false;
}
}
function doPaste(control){
maxLength = control.attributes["maxLength"].value;
value = control.value;
if(maxLength){
event.returnValue = false;
maxLength = parseInt(maxLength);
var oTR = control.document.selection.createRange();
var iInsertLength = maxLength - value.length + oTR.text.length;
var sData = window.clipboardData.getData("Text").substr(0,iInsertLength);
oTR.text = sData;
}
}
function LimitInput(control)
{
if(control.value.length > control.attributes["maxLength"].value)
{
alert("You cannot enter more than " + control.attributes["maxLength"].value + " characters into this field");
control.value = control.value.substring(0,control.attributes["maxLength"].value);
}
};