Problem

You’ve developed some custom new, edit and display application pages that are stored in the _layouts directory. Let’s assume the filenames are newform.aspx, dispform.aspx and editform.aspx. You now want to change the properties of your custom list so that any new, edit or display requests point to your custom pages.

If you open up your site within SharePoint Designer and expand the Lists folder, from there you can access the properties of that custom list by right clicking on it and selecting Properties. Once the List Properties pane is open, click on the Supporting Files tab.

You’ll see there that you can actually choose what display, edit and new forms you want your list to be using. So if you’ve developed something custom, you would click Browse…, point to your new location and select the file. Here is where it starts to fail, I am only able to browse within the site itself and cannot navigate to my _layouts directory to select the files I mentioned above.

Solution

I suspected this was a limitation of the SharePoint Designer UI and not actually a limitation of the Object Model. I did a bit of fiddling with my SPList object and was not able to find anything that let me change those properties. If you look at the screenshot above, there is a key piece of information that’ll make the light turn on (at least it did for me). The Supporting Files tab has a drop down selector for the Content type specific forms. If you think about that a moment, you’ll remember that ALL lists within SharePoint inherit from a default content type.

So armed with that knowledge, I created an SPContentType object and took a look at it’s properties and methods. Sure enough, there are 3 properties I can set to change these forms: EditFormUrl, NewFormUrl and DisplayFormUrl. Here is some sample code I used to change the forms for my custom list called ‘My List’ that inherits from the Item content type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SPWeb web = SPContext.Current.Web;
 
web.AllowUnsafeUpdates = true;
 
SPList list = web.Lists["My List"];
 
SPContentType ct = list.ContentTypes["Item"];
 
ct.EditFormUrl = "_layouts/editform.aspx";
ct.NewFormUrl = "_layouts/newform.aspx";
ct.DisplayFormUrl = "_layouts/dispform.aspx";
 
ct.Update();
list.Update();

So in the example code above, I had to determine what default content type my list inherited from. In this case it was the Item content type.

NOTE: If you need to change the content type directly, you can definitely do that, however, I did a bit of testing and found that any lists ALREADY inheriting from that content type did not pickup my changes to the form locations. Any NEW list that I created that was inheriting from that content type did pickup the changes. In order for me to change the form locations for the existing list I had to use the code above. For reference, here is how you would change the content type directly. The code has only a subtle difference.

1
2
3
4
5
6
7
8
9
10
11
SPWeb web = SPContext.Current.Web;
 
web.AllowUnsafeUpdates = true;
 
SPContentType ct = web.ContentTypes["Name Of Your Content Type"];
 
ct.EditFormUrl = "_layouts/editform.aspx";
ct.NewFormUrl = "_layouts/newform.aspx";
ct.DisplayFormUrl = "_layouts/dispform.aspx";
 
ct.Update();

As always, any questions, let me know!

Tagged with:  

Hey all, I just wanted to point out a sneaky little problem we ran into today at work. I’ll outline it briefly here, but Donabel wrote a great summary that we’ve posted on the Black Ninja blog.

The problem to be aware of is related to SPQuery objects that have the LookupId set to TRUE and the Value Type set to Lookup. An example:

1
2
3
4
SPQuery oQuery = new SPQuery();
 
oQuery.Query = "<Where><Eq><FieldRef Name='Employee' LookupId='TRUE'/>" +
            "<Value Type='Lookup'>161</Value></Eq></Where>";

With a NON indexed column, the above query works fine. Once you change the Employee field so it is an indexed column, the above query will fail to work. Changing Value Type from Lookup to Integer seems to make it work again. So somehow, something is changing when a column is indexed thus causing the query to behave strangely.

Removing the index on that column makes everything work magically again. Thanks to the following post for affirming what we had already painfully discovered.

Tagged with:  

I recently wrote an article that outlined a recent optimization effort using the ANTS Profiler and it’s finally been published! If you’re having any performance issues at all in your custom SharePoint Applications, you should take a few minutes and check this out. My aim was to share my experiences and hopefully to generate some interest in application profiling.

http://www.simple-talk.com/dotnet/.net-tools/working-with-the-ants-profiler-to-optimize-sharepoint/

Tagged with:  

Ok, if you’re like me, writing CAML queries can sometimes make your head hurt. Not because it’s terribly complicated, but because there’s not much useful information out there that demonstrates, with REAL examples, how these should be written. So let’s see if we can clarify this:

Scenario 1

Get me all items in a list WHERE fullName equals the currently logged in user.

1
2
3
4
5
6
7
8
9
10
11
SPWeb web = SPControl.GetContextWeb(Context);
 
string fullName = web.CurrentUser.Name;
 
SPQuery oQuery = new SPQuery();
 
oQuery.Query = 
    "<Where>" + 
    "<Eq><FieldRef Name='FullName'/><Value Type='Text'>'" + fullName + "'</Value></Eq>" + 
    "</Where>" +
    "<OrderBy><FieldRef Name='StartTime' Ascending='FALSE'></FieldRef></OrderBy>";

Scenario 2

Get me all items in a list WHERE fullName equals the currently logged in user AND status equals ‘Complete’.

1
2
3
4
5
6
7
8
9
10
11
12
13
SPWeb web = SPControl.GetContextWeb(Context);
 
string fullName = web.CurrentUser.Name;
 
SPQuery oQuery = new SPQuery();
oQuery.Query = 
    "<Where>" +
    "<And>" +
        "<Eq><FieldRef Name='FullName'/><Value Type='Text'>'" + fullName + "'</Value></Eq>" +
        "<Eq><FieldRef Name='Status'/><Value Type='Text'>Complete</Value></Eq>" + 
    "</And>" +
    "</Where>" +
    "<OrderBy><FieldRef Name='StartTime' Ascending='FALSE'></FieldRef></OrderBy>";

Scenario 3

Get me all items in a list WHERE fullName equals the currently logged in user AND status equals ‘Complete’ AND manager is James Lane.

This is where it gets a bit tricky. The following example is INCORRECT and will produce an error when run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SPWeb web = SPControl.GetContextWeb(Context);
 
string fullName = web.CurrentUser.Name;
 
SPQuery oQuery = new SPQuery();
oQuery.Query = 
    "<Where>" +
    "<And>" +
        "<Eq><FieldRef Name='FullName'/><Value Type='Text'>'" + fullName + "'</Value></Eq>" +
        "<Eq><FieldRef Name='Status'/><Value Type='Text'>Complete</Value></Eq>" +
        "<Eq><FieldRef Name='Manager'/><Value Type='Text'>James Lane</Value></Eq>" +
    "</And>" +
    "</Where>" +
    "<OrderBy><FieldRef Name='StartTime' Ascending='FALSE'></FieldRef></OrderBy>";

This is the correct way to do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SPWeb web = SPControl.GetContextWeb(Context);
 
string fullName = web.CurrentUser.Name;
 
SPQuery oQuery = new SPQuery();
oQuery.Query = 
    "<Where>" +
    "<And>" +
        "<And>" +
            "<Eq><FieldRef Name='FullName'/><Value Type='Text'>'" + fullName + "'</Value></Eq>" +
            "<Eq><FieldRef Name='Status'/><Value Type='Text'>Complete</Value></Eq>" +
        "</And>" +
        "<Eq><FieldRef Name='Manager'/><Value Type='Text'>James Lane</Value></Eq>" +
    "</And>" +
    "</Where>" +
    "<OrderBy><FieldRef Name='StartTime' Ascending='FALSE'></FieldRef></OrderBy>";

Scenario 4

Get me all items WHERE fullName equals the currently logged in user AND status equals ‘Complete’ OR status equals ‘On Hold’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SPWeb web = SPControl.GetContextWeb(Context);
 
string fullName = web.CurrentUser.Name;
 
SPQuery oQuery = new SPQuery();
oQuery.Query = 
    "<Where>" +
    "<And>" +
        "<Eq><FieldRef Name='FullName'/><Value Type='Text'>'" + fullName + "'</Value></Eq>" +
        "<Or>" +
            "<Eq><FieldRef Name='Status'/><Value Type='Text'>Complete</Value></Eq>" +
            "<Eq><FieldRef Name='Status'/><Value Type='Text'>On Hold</Value></Eq>" +
        "</Or>" +
    "</And>" +
    "</Where>" +
    "<OrderBy><FieldRef Name='StartTime' Ascending='FALSE'></FieldRef></OrderBy>";

I really do recommend if you’re doing any work with CAML queries that you download the U2U CAML Query Builder 2007. It doesn’t come without it’s issues, for example, doing a query on a list with a column of type Lookup does not always yield the results I would expect, but it’s still quite helpful. I also often find myself changing the Value Type from Lookup to Text but other than that it’s a huge resource when trying to determine if the data is actually in the list or if there is a problem with the query i’ve written.

Any feedback or questions, please drop me a line.

Tagged with:  

Developing custom web applications, workflows or web parts within SharePoint require some level of error handling to ensure smooth operation of your custom solution.

There are several techniques for doing this such as writing to a file or to the event log, but I came across a fantastic article a while back and just recently came around to trying it out. I’m happy to report that it works quite well.

The source of this information comes from the Blue Surf Tech blog titled The Sharepoint way for Application Error Alerts.

Now just to summarize the technique here:

1. Create a SharePoint custom list with custom columns based on the type of information you want to capture. For example you may want to capture the exception source, stack trace or inner exception to name a few. Or you may simply want to log informational messages that indicate areas of success in your code. The link above does a great job of describing the former, I’ll outline the latter in the next few steps.

2. The next step is to write the method that will log the information messages at specific intervals within your code to the custom SharePoint list you just built. Here is what my LogInfo method looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void LogInfo(string message, string sItemUrl, int sItemID, string sTitle)
{
    // create an handle to the current web context (works only in application pages)
    SPWeb web = SPContext.Current.Web;
 
    // create new item
    SPListItem item = web.Lists["Application Log"].Items.Add();
 
    item["Title"] = sTitle;
    item["Application"] = "SharePoint Employee List - Edit Form";
    item["Url"] = sItemUrl;
    item["ItemID"] = sItemID;
    item["Message"] = message;
    item["User"] = web.CurrentUser.Name;
 
    // save
    item.Update();
}

3. Finally, I just pick specific areas in my methods or workflow, for example an button submit event or a SendEmail method, to log the successes or any other type of message that might be helpful to track.

1
LogInfo("Successful item update.", listItem.Url, listItem.ID, listItem.Title);

There you have it, let me know how it works out. Feedback is most welcome.

Tagged with:  

Ok, so we all know that ASP.NET controls, or any control for that matter with the runat=”server” tag, will generate a funky client ID when rendered on the page.

Sometimes, we need to get at those controls via some client side javascript so that we may manipulate their properties or simply get/set their values.

If you’re not already familiar with JQuery, you need to take 5 minutes of your day and read up on it. It’s a very light javascript library that any decent developer should have in their toolbelt.

http://jquery.com/

Now getting started with JQuery is pretty simple. It doesn’t matter what type of app you’re building, you simply need to download the jquery-1.3.2.min.js (this is the current version as of the time i wrote this post) file and include it in the HEAD tag your page:

1
<script type="text/javascript" src="jquery-1.3.2.min.js"></script>

Some examples of what you can do with JQeury (I got most of this from reading the documentation):

Example 1 – DOM Ready

1
2
3
4
$(document).ready(function () {
    // execute any code here that you'd like to be run as soon as the dom is ready. You may often
    // want to show/hide certain controls on page load
});

Example 2 – Determine if an ASP.NET radio button is checked

1
2
3
var radio = $("#<%= radioButton.ClientID %>");
var domRadio = radio[0];
var isChecked = domRadio.checked;
1
<input type="radio" id="radioButton" name="age" value="20" runat="server" />

This is the most important part:

1
$("#<%= radioButton.ClientID %>")

radioButton.ClientID gets replaced with the automatically generated ASP.NET client id of this control. When I was working with prototypejs, I used to use the class selector to get a handle to these controls: $(“.className”) so naturally I started doing the same thing when I began working with JQuery. I wasn’t entirely happy with this method because it added what was otherwise an unnecessary property to my controls.

After discovering .ClientID, I use it most often. I still use the class selector but for different reasons. For example, if i want to show/hide a specific TR tag within a table, here is how I would do that:

Example 3 – Show/Hide

1
2
3
4
5
if (isChecked) {
    $(".rowHide").hide();
} else {
    $(".rowHide").show();
}
1
2
3
<tr class="rowHide">
    <td>some data</td>
</tr>

Example 4 – Set the url for a link tag

1
2
3
$("#<%= link.ClientID %>").attr({
    href: "/pages/mypage.aspx?id=" + id;
});
<a id="link" target="_blank" runat="server">My Link</a>

I’ll be writing a post that describes in detail how to leverage JQuery to build autocomplete functionality into your SharePoint applications. Some users prefer the ASP.NET Ajax route, but in my humble opinion, JQuery is far easier to configure and use.

If anyone would like to see that sooner, just ping me and I’ll send some notes of how I achieved this.

Tagged with:  

This post is an update to a previous post I had written on validating the PeopleEditor control on post back. In that particular post, I described the challenges of getting a PeopleEditor control, within a custom application page, to validate correctly as a required field on the client side (I had server side validation already in place and working correctly).

For the most part, the solution I outlined worked well, except for some situations where other controls on the page were performing their own post back (for example, a data grid in edit mode). I found that on submit of the entire form, the ASP.NET required field validator for the PeopleEditor control would kick in informing me that the field was required. However, there was a valid entry in the control so I could only conclude that the post back by some other control on the page was mucking up the view state for the PeopleEditor control and on submit the form was no longer aware that this field did in fact have a value in it.

The MSDN article found here outlines my problem exactly with the eventual solution further down in the thread. I just wanted to highlight it here because it was exactly what I needed to resolve my issue. Thank you to Jan Lange for posting his solution!

1
2
3
4
5
6
7
8
<script language="javascript">  
    function CheckProjectManager(source, arguments) {  
        if (aspnetForm.ctl00_PlaceHolderMain_ProjectManager_downlevelTextBox.value == "")  
            arguments.IsValid = false;  
        else  
            arguments.IsValid = true;  
    }  
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<SharePoint:PeopleEditor   
                ID="ProjectManager"   
                AllowEmpty="false"   
                ValidatorEnabled="true"   
                MultiSelect="false" 
                runat="server"   
                SelectionSet="User" 
                Width="200px"   
                TabIndex="2" />
 
<asp:CustomValidator  
                ID="rfvProjectManager" 
                runat="server" 
                ControlToValidate="" 
                ErrorMessage="Required" 
                Enabled="true"   
                ClientValidationFunction="CheckProjectManager">  
                </asp:CustomValidator>

Just to outline, what Jan eventually did was create a custom validator that called a javascript function to validate the control. Within that function he checks to see if the control is empty and stores the results of the validation in the IsValid property of the ServerValidateEventArgs object. Notice that he also sets the ControlToValidate property to “”.

Hope this helps!

Tagged with:  

Hey all you developers out there working with Microsoft Office SharePoint Server, be sure to check out the developer challenge starting on March 1st! And don’t forget to read the eligibility and entry requirements first to find out how to participate.

I’ve never heard of Microsoft running one of these before but I am excited to participate!

Tagged with:  

I was recently working on a Black Ninja Software project for a client of ours where performance became an issues for one of the custom application pages we had developed. I plan to write up a more detailed report on how I managed to cut the load times of this particular page in half using the ANTS Profiler, but for now I wanted to highlight a piece of the puzzle that I discovered today.

I won’t cover how to load values into a PeopleEditor control from within a SharePoint list, you can view that in more detail here.

Take a look at the example below:

1
2
3
SPFieldUserValue user = new SPFieldUserValue(web, Convert.ToString(listItem["Employee]));
 
peEmployee.CommaSeparatedAccounts = user.LookupValue;

If we have an SPFieldUserValue object, calling upon the LookupValue property will return the Name of that user. This is not to be confused with the LoginName property. Assigning that to the CommaSeparatedAccounts property may do the trick and will load that user account into the control but not without a performance hit.

A better approach would be:

1
2
3
SPFieldUserValue user = new SPFieldUserValue(web, Convert.ToString(listItem["Employee"]));
 
peEmployee.CommaSeparatedAccounts = user.User.LoginName;

The difference is minor. Instead of using the LookupValue property, we leverage the SPUser object and call upon the LoginName property. In all of my testing, I noticed an improvement in speed when using the LoginName property.

Depending on your environment, the output from either of those properties will differ and that’s the heart of the performance issues. In my environment, LoginName and Name outputted the following:

Name – “Joe User”
LoginName – “domain\juser”

Having the domain specified seems to speed this whole process up. I would be interested to hear from anyone else who’s encountered anything similar.

Tagged with:  

Attatching a Content DB to a Web Application

On February 13, 2009, in SharePoint 2007, by shereen

For each Web Application you create in SharePoint, by default, there is a single Content DB that is assigned to it. Typically, the steps for creating a web application are as follows:

  1. Launch your Central Administration web site.
  2. On the Quick Launch bar to the left of the page, click on Application Management.
  3. Under the SharePoint Web Application Management heading, click on Create or extend Web application.
  4. Click on Create a new Web Applicaiton.
  5. The Create New Web Application page requires some information before creating your new web application. I won’t go through all of this in detail, but let’s pay particular attention to the Database Name and Authentication section. I have attempted in the past to alter the database name to match the already existing content db i would like this web application to use. This is not recommended nor does it work consistently. What you’ll want to do is accept all defaults for the database name and proceed with creating your web application. In the next steps we’ll outline how you swap this content db out for the one your already existing content db.

Now once your content db has been created, you’ll need to go back to the Application Management screen. From there, try the following:

  1. Click on Content databases.
  2. On the right of the toolbar, look for the Web Application drop down. Make sure you change this to match the web app you’re trying to replace the content db for. I find that sometimes this is defaulted to the Central Admin web application which is not what we want.
  3. Click on the database name link, this should bring up the Manage Content Database Settings page. Change your Database status to Offline and check Remove content database. Click OK. This content db shouldn’t contain anything since we just created this web application.
  4. We should now be redirected back to the Manage Content Databases page. Click on Add a content database.
  5. The only thing you need to change on this page is the Database Name field. Set this to the name of the content db you want to add. Set the search server and click OK.

Now typically this is a smooth operation, however, if you encounter the error below:

Attaching this database requires an upgrade, which could time out the browser session. You must use the STSADM command ‘addcontentdb’ to attach this database.

Open up a command prompt window, and type the following stsadm command in:

stsadm -o addcontentdb -url http://server01/ -databasename WSS_Content

There are other arguments you can use for this command, to see a list of those, type:

stsadm -o addcontentdb

Once the command has completed successfully, try loading your site. If you have any issues at all with this, drop me a line.

Tagged with: