Home | Blog | Screencasts | Projects
# Thursday, August 28, 2008

In my first post dissecting the portal connection code, I showed the following code:

 

PortalConfig portalConfig = 
(PortalConfig)curSite.WebApplication.Farm.GetObject
(
    new Guid(curSite.WebApplication.Properties[PROP_KEY].ToString())
);

 

Notice the line: curSite.WebApplication.Properties[PROP_KEY].ToString()

The properties collection is scoped at the web application level. So if someone was to build a tool that called the Clear() method on this collection it would cause issues inside the Portal Connection tool.

So my point is to use the web application properties with other applications in mind, only clear your own state.

Thursday, August 28, 2008 8:01:40 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work
# Wednesday, August 27, 2008

This is the follow up article on the SPPortalConnection tool that I recently introduced.

So taking a look at how I've broken the solution up, you'll see that firstly I've got the PortalConnection project which is a class library project, the second project 'SPPortalConnectionSolution' has all the bits to make a Sharepoint solution by running the CreateSolution.cmd batch file.

So starting with the PortalConnection project, the first file ManagePortalConnectionPage.cs is the code behind page for the central admin interface. The most interesting method in this page is the logic to apply the portal connection:

image

 

 

private void ProcessStaple(SPWebApplication webApp)
{
    PortalConfig portalConfig = null;
    Guid stapleGuid = new Guid(SPPortalConnectionFeatureReciever.STAPLE_GUID);

    SPFeature portalConnectionStaple = webApp.Features[stapleGuid];
    try
    {                
        //set property  
        if (webApp.Properties.ContainsKey(SPPortalConnectionFeatureReciever.PROP_KEY))
        {
            portalConfig = (PortalConfig)webApp.Farm.GetObject(
            new Guid(webApp.Properties[SPPortalConnectionFeatureReciever.PROP_KEY].ToString()));
        }
        else
        {
            Guid guid = Guid.NewGuid();
            webApp.Properties.Add(SPPortalConnectionFeatureReciever.PROP_KEY, guid);
            portalConfig = new PortalConfig(webApp.DisplayName, webApp.Farm, guid);
        }

        portalConfig.PortalName = txtPortalName.Text.Trim();
        portalConfig.PortalUrl = txtPortalUrl.Text.Trim();
        
        portalConfig.Update();
        portalConfig.Provision();

        //activate feature
        if (portalConnectionStaple == null)
            webApp.Features.Add(stapleGuid);
        
        webApp.Update();
        BindData(webApp);
    }
    catch (Exception ex)
    {
        throw ex;
        
    }
}

 

The second file is the SPPortalConnectionFeatureReciever.cs, this class derives from SPFeature and is called each time a new site is created, this is the feature staple.

 

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    using (SPWeb curWeb = (SPWeb)properties.Feature.Parent)
    {
        SPSite curSite = curWeb.Site;                
        if (curSite.WebApplication.Properties.ContainsKey(PROP_KEY))
        {
            ApplyPortalConnectionSettings(curWeb, curSite);
        }
    }
}

 

The Feature folder SPPortalConnectionStaple contains the feature and manifest definitions.

The Elements.xml file declares the feature staple, notice the use of Global for the TemplateName (not GLOBAL#1):

 

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <FeatureSiteTemplateAssociation Id="7D174E8A-E68E-4a9b-9462-1A65301F7317" TemplateName="GLOBAL"/>
</Elements>
 

So feel free to download the source code and have a play.

 
 
Wednesday, August 27, 2008 8:38:50 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback

# Tuesday, August 26, 2008

If your profile properties includes the organisation hierarchy, you may have noticed that when an AD account is deleted or deactivated the manager of that person is sent an email notifying the manager that the site is scheduled for deletion and that they now have permissions to the mysite so they can clean it up. Of course the MySite will only be deleted if the Automatic Site Deletion is enabled. Be aware that if the AD account is deleted and Automatic Site Deletion is enabled, the manager won't have access because the site will be deleted.

image

Lets say you work in an organisation that wants to suppress this email (I know it sounds silly), how would you go about that? The first thing to understand is the process that caused that email to be sent in the first place.

The first step is the profile import will detect if the AD account has been deleted or disabled and will change the bDeleted flag to 1 in the UserProfile_Full table of the SSP database.

 

The next step of the process is handled by the My Site Cleanup Job:

 

image

 

If you disable this job then you will prevent the notification email being sent. However you will still need to have a process in place to clean out the mysites and the profiles. The crawler will still find the profiles and index them (so your people searches will include outdated people). The MySites will still be around taking up disk space.

One potential solution is to call the profile stored procedure manually:

 

DECLARE @UserID uniqueidentifier
DECLARE @NTName nvarchar(400)
DECLARE @SID varbinary(512)

DECLARE userCursor CURSOR FOR
SELECT Userid, NTName, SID from userprofile _full where bdeleted = 1

OPEN userCursor

FETCH NEXT FROM userCursor INTO @UserID, @NTName, @SID

WHILE @@FETCH_STATUS = 0

BEGIN

EXEC profile_RemoveUser @UserID, @NTName, @SID

FETCH NEXT FROM userCursor INTO @UserID, @NTName, @SID

END

CLOSE userCursor
DEALLOCATE userCursor
Tuesday, August 26, 2008 9:48:14 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work
# Sunday, August 24, 2008

I've had the chance to see two different approaches to implementing MOSS search, the first is the standard of the box experience and the second is by making use of all the features that MOSS offers. Once you understand what MOSS search can do, there is no reason why you shouldn't be making full use of the product.

I found this fantastic article called Plan the end-user search experience, which outlines in great detail all the options the search products provide.

 

I thought that I would list a few small things that give big bang for the effort that is needed to get them running.

 

1. Best Bets

 

Number 4 on the above image is the best bet. A best bet is predefined link that is shown for certain search terms. It doesn't have to be a link to your MOSS site, it can point to external resources.

 

2. Synonyms

Synonyms should be configured with words that are commonly used in your organisation, in the example above 'oof' is used for 'out of office', so both search items 'oof' and 'out of office' would show the same best bet.

 

3. Search Results Display

The layout options are not mentioned in the referenced article, I do think it is a good idea to build your XSLT to make your results look like Google. So align your results to the left, get rid of the horizontal line and maybe think about getting rid of the file type icon.

 

If anything I just want to bring the search experience to your attention, it's worth spending some time to become familiar with the various options that exist.

Sunday, August 24, 2008 10:30:37 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Sharepoint
# Saturday, August 23, 2008

In a previous post I introduced the Automatic Portal Connection tool, which can be used to automatically setup the portal connection on new and existing sites.

I thought I would take a peek at how I've made this happen:

Firstly I'd like to introduce the SPPersistedObject:

The description MSDN uses is:

 

The SPPersistedObject class provides a base class for all administration objects. It serializes all fields marked with the Persisted attribute to XML and writes the XML blob to the configuration database. The SPPersistedObject class contains code to serialize all its members that are base types, other persisted objects, and collections of persisted objects. Configuration data that is stored in persisted objects is automatically made available to every process on every server in the farm.

 

The Portal Connection tool has a class called PortalConfig which derives from SPPersistedObject this class holds the configuration information such as the portal name and portal URL:

 

/// <summary>
/// Simple persisted object that stores the portal connection name and the portal connection url
/// </summary>
public class PortalConfig : SPPersistedObject
{
    [Persisted]
    private string portalName = string.Empty;

    public string PortalName
    {
        get { return portalName; }
        set { portalName = value; }
    }

    [Persisted]
    private string portalUrl = string.Empty;

    public string PortalUrl
    {
        get { return portalUrl; }
        set { portalUrl = value; }
    }

    public PortalConfig()
    {

    }
    public PortalConfig(string name, SPPersistedObject parent, Guid guid)
        : base(name, parent, guid)
    {

    }
}

SPPersistedObject is a very handy class, especially for configuration type scenario that is used in this solution, a method that makes use of this object is:

/// <summary>
        /// Simple method to persist our settings to the site
        /// </summary>
        /// <param name="curWeb"></param>
        /// <param name="curSite"></param>
        public static void ApplyPortalConnectionSettings(SPWeb curWeb, SPSite curSite)
        {
            PortalConfig portalConfig = (PortalConfig)curSite.WebApplication.Farm.GetObject(
                                        new Guid(curSite.WebApplication.Properties[PROP_KEY].ToString())
                                        );

            if (curWeb.IsRootWeb)
            {
                curSite.PortalName = portalConfig.PortalName;
                curSite.PortalUrl = portalConfig.PortalUrl;
            }
            curWeb.Update();
        }

 

This object is filled out and persisted by the central admin page, then a feature staple gets this information when the site is created and applies it, but I'll expand on this in future posts.

Saturday, August 23, 2008 11:08:40 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work
# Thursday, August 21, 2008

The portal connection is pretty useful, say you have a web application for team sites and have self service site creation turned on you would probably like the portal connection to be set to your portal homepage. Just as a refresher the portal connection is the link at the top of your site:

image

 

I've put together a tool that will give you the ability to set the portal connection for new sites and also for newly created sites. Once the solution has been deployed and the features activated, from central admin you will see a new link:

image

Following this link you will be presented with the following page:

image

From here you can select the web application to work with and you can set the portal name and URL. By selecting the 'Apply To Existing Sites' checkbox, you will set portal connections to all the sites on that web application.

The SharePoint solution can be downloaded here, I'll be releasing the code soon, I want to release it to Codeplex, I just need to set it up first.

Here is a small screencast showing the tool in use

 

Edit: I've now setup the Codeplex project: http://codeplex.com/SPPortalConnection

Thursday, August 21, 2008 10:36:14 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work
# Tuesday, August 19, 2008

If your experiencing long initial page load times and your servers are configured so they can't connect out to the internet, you can reduce this time by either allowing the servers access to crl.microsoft.com or by deselecting the 'Check for publisher's certificate revocation' option from within Internet Explorer. The Internet Explorer option can be found under the Tools menu, then Internet Options:

 

image

 

Another option is to add a host file entry so that crl.microsoft.com points to the localhost (127.0.0.1)

Tuesday, August 19, 2008 11:53:12 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Sharepoint | Work
# Monday, August 18, 2008

There is so much great content on MSDN, but often it's hidden away, so I like to blog about these finds so I have a chance to find them in the future.

Today's find is the Design Logical Architecture for Collaboration Sites.

In particular I like the advice surrounding team sites:

 

Design guidance for team sites includes the following recommendations, each of which is elaborated in the following sections:

  • Host team sites in a dedicated Web application.

  • Apply Web application general settings, such as quota and life-cycle management settings at the Web application level to manage growth of team sites and to keep content current.

  • Design content database settings for appropriate storage and scale and to ensure that you can back up and restore databases of the designed size.

  • Automatically delete sites that are not used.

  • Use paths to organize team site URLs.

  • Plan for appropriate policies and permissions.

 

Some of my notes:

  • Hosting the team sites in a dedicated web app - This gives you the power to apply web application policy (this goes for the last point as well), what we normally do is have a zone like admin.yourteamsiteurl, then we use policy so that our administrators have full access over the web application. This means they can go into any team site if needed.
  • In large organisations keeping an eye on the growth of the team sites is important, you need to know how large the content database is and when to expand out to more databases. Make sure you setup site level warnings, so your administrators are on top of the situation.
  • Ensure that the procedures for notifying users of inactive sites is occurring, you really want to be able to claim that space back.
  • A nice path is helpful for your end users, we normally opt for team.yoursiteurl/sites/<teamsitename> (the default setting)

This article also feeds into another great find Logical Architecture Sample Design: Collaboration Sites

Monday, August 18, 2008 11:00:45 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [1] - Trackback
Sharepoint | Work
# Sunday, August 17, 2008

JQuery is my all time favorite JavaScript library, it's so simple to use and incredibly powerful, but if you include all the plugins that are available, I just don't think that you would find a better tool for client side scripting.

My favorite validation plugin is the 'Validation Plugin', let me give you an example of how it is used:

Lets say you have a signup form, you want the user to select a user name, a password with confirmation and an email address, assume all of this is inside a form tag named 'signup'.

All of the HTML input element ids are 'Password', 'ConfirmPassword', 'Email'.

The JavaScript would be:

 

<script type="text/javascript">
$().ready(function() {
$("#signup").validate({
        rules: {            
            UserName: {
                required: true,
                minLength: 2,
                remote: "/Account/CheckUserName"
            },
            Password: {
                required: true,
                minLength: 2
            },
            ConfirmPassword: {
                required: true,
                minLength: 5,
                equalTo: "#Password"
            },
            Email:{
                required: true,
                email: true,
                remote: "/Account/CheckEmail"
            }
        },
        messages: {
            
            UserName: {
                required: "Please enter a username",
                minLength: "Your username must consist of at least 2 characters",
                remote: jQuery.format("{0} is already in use")
            },
            Password: {
                required: "Please enter a password",
                minLength: "Your password must consist of at least 2 characters"
            },
            ConfirmPassword: {
                required: "Please provide a password",
                minLength: "Your password must be at least 2 characters long",
                equalTo: "Please enter the same password as above"
            },
            Email: {
                required: "Please enter your email address",
                minLength: "Please enter a valid email address",
                remote: jQuery.format("{0} is already in use")
            }
        }
    });
});
</script>

 

Now notice how I used the remote property with '/Account/CheckEmail' and '/Account/CheckUserName', these are URL's that will be called by the client side JavaScript, they will pass along the current value of the textbox, so you could write server side code to validate the input, an ASP.NET MVC Controller Method could look like:

 

public JsonResult CheckUserName(string username)
{
    return Json(CheckValidUsername(username));
}

 

Notice the use of a JsonResult, The ASP.NET MVC framework is great for this type of thing, combine both JQuery and the MVC framework and you have a powerful combination, Jeff Atwood has come to the same conclusion and used both of these tools to build stackoverflow.com. 

All of the above code is so clean and easy to read, can you ever remember saying that about JavaScript code?

Sunday, August 17, 2008 3:56:40 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
ASP.NET MVC | code | JQuery | Work
# Friday, August 15, 2008

Something a little less SharePoint. If you wanted to consume an RSS feed (such as my homepage) and your using .NET framework 3.5+, then you could use the following code:

 

XDocument rssFeed = XDocument.Load(@"http://httpcode.com/blogs/SyndicationService.asmx/GetRss");

var posts = from item in rssFeed.Descendants("item")
    select new
            {
                Title = item.Element("title").Value,
                Published = DateTime.Parse(item.Element("pubDate").Value),
                Url = item.Element("link").Value,

            };
//BlogPosts is a ListView
BlogPosts.DataSource = posts;
BlogPosts.DataBind();

How about taking my last 5 twitter posts, but lets remove my name:

 

XDocument twitterFeed = XDocument.Load(@"http://twitter.com/statuses/user_timeline/14554389.rss");

var titterPosts = from item in twitterFeed.Descendants("item")
            select new
            {
                Title = item.Element("title").Value.Replace("DanielPollard:","")                       

            };
//TwitterPosts is a ListView - Notice the Take(5) below
TwitterPosts.DataSource = titterPosts.Take(5);
TwitterPosts.DataBind();

 

There's a lot to love about Linq.

Friday, August 15, 2008 9:42:50 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Work
# Tuesday, August 12, 2008

I was reading this MS KB article 'How to point to a custom 404 error page in Windows SharePoint Services 3.0 or in MOSS'. I thought I'd throw together a simple administrative page to pull it all together. I've packaged this up in this solution file.

Once you active the 'Manage Custom 404 Page' feature, you will get a new menu item in the Application Management page in Central Admin:

image

From here you get a simple page that you can type the 404 page name for the selected web application:

image

 

The code is super simple:

 

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Portal.WebControls;
using Microsoft.SharePoint.Administration;
using System.Web.UI.WebControls;

namespace SP404Handler
{
    public class SP404HandlerPage : LayoutsPageBase
    {
        protected WebApplicationSelector lstWebApps;
        protected PageLevelError pageLevelError;
        protected Button ButtonDoneOK;
        protected TextBox txt404PageName;
        protected Microsoft.SharePoint.WebControls.InputFormSection I404Section;

        protected void lstWebApps_OnContextChange(object sender, EventArgs e)
        {
            try
            {
                SPWebApplication webApp = lstWebApps.CurrentItem;
                BindData(webApp);
            }
            catch (Exception err)
            {
                pageLevelError.ErrorText = err.Message;
            }

        }

        protected void DoneButtonOkClick(object sender, EventArgs e)
        {
            SPWebApplication webApp = lstWebApps.CurrentItem;
            try
            {
                webApp.FileNotFoundPage = txt404PageName.Text;
                webApp.Update();
            }
            catch (Exception ex)
            {
                pageLevelError.ErrorText = ex.Message;

            }
        }

        private void BindData(SPWebApplication webApp)
        {
            txt404PageName.Text = webApp.FileNotFoundPage;

        }
    }
}

The source can be downloaded from here, or a complied solution from here.

Tuesday, August 12, 2008 10:05:20 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work
# Monday, August 11, 2008

I read with interest the recent example solution architecture article posted on Technet. It goes through the logical architecture of a School MOSS implementation. They have some good recommendations on hosting hardware and have some interesting comments on the way they handle media hosting.

Of Course being good consultants they provide nice visio overlays of the logical architecture:

 

 

However I was surprised to see the following:

 

The solution is composed of two sets of sites:

  • Sites for Twynham students, staff, and parents

  • Sites for hosted schools

Each set of sites is grouped in a separate application pool.

Basically they are saying that staff sites and student sites are hosted in the same web application. I hope staff aren't going to put sensitive information into their sites, because it would only take one human error assigning permissions to give students access to the site. However if the students and staff are in separate web applications you could use web application policy to block students, so even if they were accidentally given access by a human, they would be blocked.

I'm sure this has been thought about by these guys because further on in the article we find:

 

You want to use zone policies to enforce permissions at the Web application level. For example, you can create a policy to deny write access to all unauthenticated users who view content on the public-facing sites.

This is a classic example of where web application policy is so helpful and why it really pays to have a think about the implications of your design.

Monday, August 11, 2008 11:23:33 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Sharepoint | Work
# Sunday, August 10, 2008

I came across this white paper titled 'Office SharePoint Server Web Browser Support', it covers the how SharePoint supports level 1 and 2 browsers, but the authors have done extensive testing on the most common features in SharePoint and provided a matrix of what does and doesn't work. They even cover some work around's like using the Telerik RAD editor for SharePoint.

I'm blogging about this as much for my own memory as anything else, you always come across these great resources and can never find them again.

Sunday, August 10, 2008 3:16:43 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Sharepoint | Work
# Friday, August 08, 2008

I came across an interesting issue the other day with a Virtual Earth web part that we have developed. If you scrolled down the page and hovered over a pushpin, in IE the info window would be displayed higher than the pushpin, i.e. they weren't aligned properly.

I found the answer to my problem, the master page that the site was using did not have the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

The missing doctype tag was putting IE in quirks mode and this caused the scroll behavior mentioned above. Heather Solomon has a discussion on Sharepoint master pages and the missing doctype. Unfortunately for us the master page couldn't easily be changed without making changes to a number of CSS classes.

I guess my point is that your sharepoint designer should know about this sort of stuff and design from the outset with the doctype tag.

Friday, August 08, 2008 10:25:29 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [1] - Trackback
Sharepoint | Work
# Wednesday, August 06, 2008

I'm sure you've done work in an organisation that wants to exert its control over users, one of the common things that I've seen is the disabling of menu items like the 'delete this web site' option.

menuitem

The last thing you want to do is edit the pages on the file system, this will just cause issues when you apply a service pack or if you bring a new WFE online because each server will need the same hack.

Instead you should build a solution that makes use of the HideCustomAction definition.

For the above delete site link, the Elements.xml file should look like:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<HideCustomAction Location="Microsoft.SharePoint.SiteSettings"
        GroupId="SiteAdministration" HideActionId="DeleteWeb"
        Id="HideDeleteWeb" />

</Elements>

From here it's just a matter of referencing the above elements.xml in your feature.xml file and building the solution in the normal way.

To find out what the ActionId, GroupId and Location you can refer to this MSDN page.

Now you can safely change the menu items, but be aware that the files still exist on the file system, so in the example above if a user still knew the location of the delete web page they could still delete their site.

Wednesday, August 06, 2008 10:39:55 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Sharepoint | Work
# Monday, August 04, 2008

Depending on your hardware if your crawling your own content, or if you are crawling external content you might like to know what the three settings for the crawler performance settings really mean.

I ran across this article Change the Indexer Performance Settings which provides the following table:

 

 

Indexer Performance setting

Total threads available for crawling

Maximum threads available for crawling any particular host

Reduced

Number of processors on the index server

Number of processors on the index server

Partially reduced

4 times the number of processors on the index server

Number of processors on the index server plus 4

Maximum

16 times the number of processors on the index server

Number of processors on the index server plus 4

Monday, August 04, 2008 11:00:46 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Sharepoint | Work
# Sunday, August 03, 2008

I had the task of writing a web part that had a lot of UI code and one of the requirements was that a designer could change the look and layout as a toolpart setting. The smartpart stuff is OK, but it doesn't really provide the kind of flexibility that I was after. I came up with a neat solution that I think is pretty powerful.

We've all seen the example webpart code that has lots of layout controls like tables defined in the CreateChildControls method, it's awful to write and its not easy to change. What we really need is a way to define a template that is provided by an external source and then to hookup our code to perform operations on it.

 

Lets have a look at some sample code:

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

namespace Template
{
    
    public class Template : System.Web.UI.WebControls.WebParts.WebPart
    {
        //string with UI code to be added to the page
        protected string templateHTML = string.Empty;

        //Control IDs, the template must use these ID's
        private string searchButtonID = "searchImgBtn";
        private string allTextID = "allTxt";
        //controls that will be found from the template
        protected TextBox allTxt = null;
        protected ImageButton searchBtn = null;

        public Template()
        {            
        }

        protected override void CreateChildControls()
        {
            if (templateHTML != string.Empty)
            {
                try
                {
                    //create a control that has been parsed by asp.net
                    Control template = Page.ParseControl(templateHTML);
                    //add it to the page
                    this.Controls.Add(template);
                    //search for the known controls
                    allTxt = template.FindControl(allTextID) as TextBox;                    
                    searchBtn = template.FindControl(searchButtonID) as ImageButton;
                    //hook up any events
                    if (searchBtn != null)
                    {
                        searchBtn.Click += new ImageClickEventHandler(btnSearch_Click);
                    }

                }
                catch (Exception err)
                {
                    this.Controls.Add(
                        new LiteralControl(string.Format("Error applying template: {0}", err.Message))
                        );
                }
            }
            else
            {
                this.Controls.Add(new LiteralControl("Please enter a template in the toolpart settings"));
            }

            base.CreateChildControls();
        }

        void btnSearch_Click(object sender, ImageClickEventArgs e)
        {
            //if the template included the textbox, show its results
            if (allTxt != null)
            {
                this.Controls.Add(new LiteralControl("Searched On: " + allTxt.Text));
            }
        }

        [System.Web.UI.WebControls.WebParts.WebBrowsable(true),
        System.Web.UI.WebControls.WebParts.WebDisplayName("Layout Template"),
        System.Web.UI.WebControls.WebParts.WebDescription("Template that is used for layout"),
        System.Web.UI.WebControls.WebParts.Personalizable(
        System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
        System.ComponentModel.Category("Template Settings"),
        System.ComponentModel.DefaultValue("")
        ]
        public string TemplateHTML
        {
            get
            {return templateHTML;}

            set
            {templateHTML = value;}
        }
        
    }
}

The template that is set with the toolpart should look like this:

 

<table>
    <tr>
        <td>Search On:</td>
        <td><asp:TextBox ID="allTxt" runat="server" />
    </tr>
    <tr>
        <td colspan="2"><asp:ImageButton runat="server" ID="searchImgBtn" ImageUrl="Search.gif"/>
    </tr>
<table>

Notice how the control IDs match the values in the code, that is important, because we use FindControl to bind the code to the UI.

So what we end up with is a flexible way to define our UI code in a way that most of us are used to. I've used this in a number of places now and haven't seen any issues, drop me a line if you start using this approach.

Sunday, August 03, 2008 1:59:28 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work

I've written a Sharepoint feature that will modify the web.config file and add the entries needed to enable the ASP.NET AJAX Control Toolkit.

 

I've made the feature scoped to the web application level, so to activate the feature (which will write to the web.config file) go to Application Management in central admin, then to Manage Web application features:

 

Next step is to activate the feature:

 

 

 

I was thinking that I would just provide the source code and that you could include it in your solution project as this functionality doesn't do much as a stand alone feature.

 

To use this feature you will need the source, I'm thinking about uploading to CodePlex, I'm just not sure if one code file is enough to warrant this.

To add this feature to your existing project you should include the following information in your feature.xml:

 

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="YOUR GUID ID"
         Scope="WebApplication"
         Title="ASP.NET Ajax web.config update tool"
         Description="Adds web.config entries to enable ASP.NET Ajax"
         ReceiverAssembly="Full name and version of your assembly"
         ReceiverClass="Features.AjaxWebConfigInstaller"
         Hidden="False"
         >

</Feature>
Sunday, August 03, 2008 1:17:56 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | Sharepoint | Work
Statistics
Total Posts: 190
This Year: 3
This Month: 0
This Week: 0
Comments: 38