Home | Blog | Screencasts | Projects
# Sunday, April 26, 2009

We needed the basic ability to redirect specific users to a different page. I first took a look at Adam Buenz excellent Redirection web part but we thought that it might be a little bit complex for our content authors to use, so I put together a really simple web part that gives the user the ability to select the users or groups via the standard SharePoint people picker interface.

 

image

 

The idea is that you can select a number of groups or users to apply the redirection to, this could be as broad as all authenticated users or refined to a single user. Of course you need a way edit the web part, so another people picker dialog allows the user to select users or groups that this redirect does not apply to. If they match the exclusion, the web part will present them with the text ‘This account was excluded from redirection to <url>’. This exclusion group is normally the content authors.

 

image

 

It is possible to put a number of these web parts on the same page, so if for example you wanted to redirect to a number of different sites based on AD group or user accounts, this should work fine.

 

First step, as always is to add the solution to SharePoint, once the solution is deployed, be sure to activate the ‘Redirector web part’ feature at the site level:

 

image

 

Once that is done, it’s just a matter of selecting the ‘Redirect web part’ from the web part gallery:

 

image

 

Hopefully you’ll find some use for it. The source code can be downloaded here and the WSP can be found here.

 

The code for this web part is fairly straight forward, I have a tool part that uses the SharePoint  PeopleEditor control. This control has a property to return the selection of groups or accounts as a comma separated string. With this string I can then do a search to see if the current user is in the group like:

        private bool IsUserIn(string commaSeperatedList)
        {
            bool isMatch = false;

            if (commaSeperatedList != string.Empty)
            {
                foreach (string group in commaSeperatedList.Split(','))
                {
                    if (Page.User.IsInRole(group) || Page.User.Identity.Name == group)
                    {
                        isMatch = true;
                    }
                }
            }
            return isMatch;
        }

 

So the web parts main logic looks like:

 

            bool redirectMatch = false;
            bool exclusionMatch = false;

            redirectMatch = IsUserIn(selectedADVal);
            exclusionMatch = IsUserIn(excludeADVal);
                   

            if (redirectMatch == true && exclusionMatch == false)
            {
                Page.Response.Redirect( redirectUrl);
            }
            else
           
{
                if (redirectMatch)
                {
                    outputCtrl.Text = "This account was excluded from redirection to " + redirectUrl;
                }
            }

Sunday, April 26, 2009 12:23:41 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | MOSS | Sharepoint
# Thursday, March 19, 2009

If you are using the BDC in your MOSS application, you may have noticed that the AUDEvent table in the SSP database can grow quite large and quickly. You might think that the stsadm command –o trimauditlog would do the trick in reducing this table, but in fact it does not work for the SSP database only content databases.

 

You can specify the audit setting in the Application Definition File. Simply add the Property ‘Audit’ to your Entity’s Properties section:

 

<Entities>
    <
Entity EstimatedInstanceCount="0" Name="dbo.Sales">
      <
Properties>
        <
Property Name="Audit" Type="System.Boolean">false</Property>
      </
Properties>

The setting of false will prevent any audit entries being written to the AUDEvent table.

Thursday, March 19, 2009 9:48:23 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
BDC | MOSS | Sharepoint | Tip
# Sunday, March 08, 2009

I thought it might be fun to play around with the people search of MOSS. I wanted to have an auto complete like experience, where you could start typing someone’s name and a list of suggestions are displayed below:

 

image

 

I started by including JQuery and the Autocomplete JQuery plugin into a style library.

 

Next I created a Handler called PeopleSearch.ashx, I placed this in the _layouts directory (or 12 hive \TEMPLATE\LAYOUTS )

The code for this handler is simple:

 

   1: <%@ WebHandler Language="C#" Class="GenericHandler1" %>
   2: <%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
   3: <%@ Assembly Name="Microsoft.Office.Server.Search, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
   4: <%@ Assembly Name="Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
   5:  
   6: using System;
   7: using System.Web;
   8: using System.Data;
   9: using Microsoft.SharePoint;
  10: using Microsoft.Office.Server.Search.Query;
  11:  
  12: public class GenericHandler1 : IHttpHandler {
  13:     
  14:     public void ProcessRequest (HttpContext context) {
  15:         context.Response.ContentType = "text/plain";
  16:     
  17:         string prefixText = context.Request["q"]; 
  18:         using (SPSite siteCollection = SPContext.Current.Site)
  19:         {
  20:             // create a new FullTextSqlQuery class
  21:             FullTextSqlQuery query = new FullTextSqlQuery(siteCollection);
  22:             query.QueryText = string.Format("SELECT Title FROM SCOPE() WHERE FREETEXT(defaultproperties, '{0}*') AND \"Scope\"='People' ", prefixText);
  23:             query.ResultTypes = ResultType.RelevantResults;
  24:             query.RowLimit = 10;
  25:  
  26:             // execute the query
  27:             ResultTableCollection queryResults = query.Execute();
  28:             ResultTable queryResultsTable = queryResults[ResultType.RelevantResults];
  29:                         
  30:     
  31:             while(queryResultsTable.Read()){
  32:                 context.Response.Write(queryResultsTable.GetString(0) +  Environment.NewLine);              
  33:             }    
  34:         }      
  35:         
  36:     }
  37:  
  38:     public bool IsReusable {
  39:         get {
  40:             return false;
  41:         }
  42:     }
  43:  
  44: }

It simply executes a full text query on the People Search Scope with the current text as the argument. Download the handler here.

 

Next I added a content Editor web part to the PeopleSearch.aspx page and included the following JavaScript (in the HTML source view):

 

   1: <script type="text/javascript" language="javascript" src="http://server/Style%20Library/jquery-1.2.6.min.js"/>
   1:  
   2: <script type="text/javascript" language="javascript" src="http://server/Style%20Library/jquery.autocomplete.min.js"/>
   3:  
   4: <script type="text/javascript">
   5:  
   6:     $(document).ready(function() {
   7:  
   8:         $("input[id*='_InputKeywords']").autocomplete("/_layouts/PeopleSearch.ashx");
   9:         $("input[id*='_InputKeywords']").attr("autocomplete","off");
  10:     
  11:     });
  12:  
</script>

 

The first thing I do is add a new attribute to prevent IE (or Firefox) from showing the previous search items. The next thing is to add the call the autocomplete plugin on the search textbox (which is found with because we know the id always ends with _InputKeywords).

 

I also added some styles from the autocomplete css to the css of the site.

 

It’s really that simple to get look ahead searching on people in MOSS. In fact this same approach could work on all the search pages.

Sunday, March 08, 2009 11:07:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [3] - Trackback
code | JQuery | MOSS
# Wednesday, February 04, 2009

I came across an interesting problem today, the MySite link was incorrect, it was working the previous day.

The first step I took was to look at the SSP, which showed that the correct My Site location was entered.

The second step was to look at the Alternative Access Mappings, again they looked OK.

By now I suspected that the ISA server was doing something funny, we had asked the network guys to publish a new rule, so I had a look at the rule settings and found the culprit:

 

linktranslation

Notice the ‘Apply link translation to this rule’ checkbox, the ISA server was looking at the HTML returned by MOSS and changing the MySite entry, it’s actually a pretty cool feature of ISA that is handy if you are accessing your web application from the internet (to your intranet). But in my case it wasn’t needed and incorrectly setup.

I thought it was an interesting problem, by logically stepping through each potential cause and eliminating it, the root cause was discovered pretty quickly.

Wednesday, February 04, 2009 4:13:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
ISA | MOSS | Sharepoint
# Thursday, January 08, 2009

A little while ago I created a simple Application Definition for working with some CRM 4 data via the BDC.

Well recently the CRM Search Accelerator for Microsoft Dynamics CRM 4 have been released, which provides a better Application Definition File:

 

The enterprise search accelerator allows Microsoft Office SharePoint Server (MOSS) customers to view and search for Microsoft Dynamics CRM data directly from their SharePoint portals. By combining these two technologies users from different areas of the business will be able to:

  • View and edit any Microsoft Dynamics CRM data such as accounts, contacts, opportunities, sales orders, invoices, service cases and any custom entity data through MOSS.
  • Launch a MOSS search which can return documents, emails, web content and Microsoft Dynamics CRM data.
Thursday, January 08, 2009 10:51:00 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
CRM | MOSS
# Monday, November 03, 2008

MSDN has a new white paper:

Planning and Monitoring SQL Server Storage for Office SharePoint Server: Performance Recommendations and Best Practices

 

It covers topics such as:

  • Database Autogrowth
  • Storage settings for the recycle bin
  • Using Quota templates
  • List performance
  • Physical Topology Guidance
  • Disk and SAN interfaces
  • Network Topology recommendations
  • Physical Storage recommendations
  • Separating database data files
  • Monitoring, Maintaining, and Troubleshooting (which provides some great performance counters that can be used for monitoring)
  • Disk and SQL recommended practices

In all it is a great resource. The direct download is here.

Monday, November 03, 2008 11:00:00 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
MOSS | Planning | WhitePaper

There are a number of options that you can use when you define your connection settings in your BDC ADF file:

The authentication methods are:

 

RevertToSelf

Simply uses the application pool account (reverts back to this account) to access the database.

 

   1: <Properties>
   2: <Property Name="AuthenticationMode" Type="System.String">RevertToSelf</Property>
   3: <Property Name="DatabaseAccessProvider" Type="System.String">SqlServer</Property>
   4: <Property Name="RdbConnection Data Source" Type="System.String">servername</Property>
   5: <Property Name="RdbConnection Initial Catalog" Type="System.String">databasename</Property>
   6: <Property Name="RdbConnection Integrated Security" Type="System.String">SSPI</Property>
   7: <Property Name="RdbConnection Pooling" Type="System.String">false</Property>
   8: </Properties>

 

PassThrough

Passes the credentials of the calling user, this will only work on a single server install or on a farm if Kerberos is enabled.

 

   1: <Properties>
   2: <Property Name="AuthenticationMode" Type="System.String">PassThrough</Property>
   3: <Property Name="DatabaseAccessProvider" Type="System.String">SqlServer</Property>
   4: <Property Name="RdbConnection Data Source" Type="System.String">servername</Property>
   5: <Property Name="RdbConnection Initial Catalog" Type="System.String">databasename</Property>
   6: <Property Name="RdbConnection Integrated Security" Type="System.String">SSPI</Property>
   7: <Property Name="RdbConnection Pooling" Type="System.String">false</Property>
   8: </Properties>

 

SQL Authentication

It is still possible to use SQL Server Authentication, the following example uses the RdbConnection properties for this:

   1: <Properties>
   2: <Property Name="AuthenticationMode" Type="Microsoft.Office.Server.ApplicationRegistry.SystemSpecific.Db.DbAuthenticationMode">
   3: RevertToSelf</Property>
   4: <Property Name="DatabaseAccessProvider" Type="Microsoft.Office.Server.ApplicationRegistry.SystemSpecific.Db.DbAccessProvider">
   5: SqlServer</Property>
   6: <Property Name="RdbConnection Data Source" Type="System.String">servername</Property>
   7: <Property Name="RdbConnection Initial Catalog" Type="System.String">databasename</Property> 
   8: <Property Name="RdbConnection Integrated Security" Type="System.String">false</Property>
   9: <Property Name="RdbConnection User ID" Type="System.String">username</Property>
  10: <Property Name="RdbConnection Password" Type="System.String">password</Property>
  11: </Properties>
  12:  

 

Single Sign On

If your using SSO, this is also supported:

   1: <Properties>
   2: <Property Name="AuthenticationMode" Type="System.String">RdbCredentials</Property>
   3: <Property Name="DatabaseAccessProvider" Type="System.String">SqlServer</Property>
   4: <Property Name="RdbConnection Data Source" Type="System.String">servername</Property>
   5: <Property Name="RdbConnection Initial Catalog" Type="System.String">databasename</Property>
   6: <Property Name="RdbConnection Integrated Security" Type="System.String">false</Property>
   7: <Property Name="RdbConnection Pooling" Type="System.String">true</Property>
   8: <Property Name="SsoApplicationId" Type="System.String">SSO Application you created</Property>
   9: <Property Name="SsoProviderImplementation" Type="System.String">Microsoft.SharePoint.Portal.SingleSignon.SpsSsoProvider,
Microsoft.SharePoint.Portal.SingleSignon, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Property>
  10: </Properties>

 

The MSDN documentation can be found here.

Monday, November 03, 2008 10:25:00 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
BDC | MOSS
# Tuesday, October 28, 2008

The BDC isn’t just limited to data located in a database, it’s most powerful feature is its ability to call webservices. I previously posted a brief description of the types of webservices are easily consumed by the BDC.

The key to easily calling webservices from the BDC first starts with an understanding of the types of methods that the BDC supports:

  • Finder 
  • Specific Finder
  • IDEnumerator

If we take the common example of getting customers from a webservice we might can start thinking about the way we create the webservice so that it doesn’t become a painful exercise.

The first method we should think about is something that can return the primary key (or ID) of our data:

So a method like:

GetCustomerIDs  - This will return customer ID’s and will become our IDEnumerator

So the BDC will next need a method that will accept an ID that was returned by our GetCustomerIDs method above.

GetCustomerByID  - This will become a Specific finder method, the naming ‘specific’ really gives the game away, it’s specifically returning data based on an ID.

The final method is a generic finder method that can be used by the Business Data List webpart:

GetCustomers – This becomes a finder method, we can have more of these methods with each one returning a different subset of data as needed.

 

It’s fairly easy to now create a BDC Application Definition File (ADF) using a tool like BDC Meta-Man. But you also need to remember that the ADF references a .NET assembly. This assembly is the proxy to the webservice, this proxy is the exact same proxy that we get automatically generated for us by Visual Studio when we add a web reference.

So you can create a proxy that will be used by the BDC, you just create a web reference in visual studio and compile that into a signed assembly and make sure the ADF references this assembly correctly.

Once you perform the two steps of creating the ADF and corresponding proxy classes, your well on your way to using the BDC via webservices.

Tuesday, October 28, 2008 1:10:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
BDC | MOSS
# Thursday, October 23, 2008

One of the cool things that MOSS offers is the ability to display the people search results ordered by social distance. You can see what result format was returned by looking at the search action links web part:

 

socialdisresults

The default people search results view can be changed in the Core results web part:

 

coreresultswp

 

The MSDN SharePoint blog has a detailed post which outlines how the colleague connections are formed:

  • Immediate colleagues which are formed using the manager profile field.
  • Colleagues added by you
  • Suggested colleagues

It’s also an interesting read to find out some small details like:

  • The first 3 pages of search results are grouped by colleague-ness: first your colleagues appear, then colleagues of your colleagues, then everyone else.
  • Within each group, the ordering is still by relevance.
  • When paging through results, another 3 pages of results will be grouped once you reach page 4, then page 7, etc.

 

Overall I think the feature works extremely well, although I’ve seen some users struggle with the feature, these users were typically expecting the results to be in alphabetical order like their previous pre-MOSS system. While I don’t agree with the concept since the results are returned by relevance (but in a social context) it is possible to sort the results alphabetically, Paul Galvin has posted some XSLT that does this. Remember that this is done outside of the search engine itself, so the XSLT is only going to sort the results per page. So its possible to have page 1 contain 10 results ordered alphabetically, then page 2 will contain 10 results that are again sorted alphabetically, which might cause problems to some users.

I think ultimately these users just need a little bit of training to understand the social distance format. Just like any search engine if you don’t get the results you are looking for on the first page, I really think you need to refine your search. If your just looking for a colleagues details the social distance is fantastic and saves lots of time, my experience is that I use the people search 90% of the time to find colleagues, of course your situation may be different.

Thursday, October 23, 2008 7:39:14 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
MOSS | Search
# Tuesday, October 14, 2008

I previously blogged about using the BDC with MOSS user profiles and how to set that whole process up. Well I thought that I might write a little about the BDC application definition file (ADF) that is imported into MOSS and is used by the BDC to generate the meta-data and to ultimately connect to the data source.

An ADF file contains metadata describing entities and methods to populate those entities.

These are the methods that are of interest to us, all have nice relevant names:

 

IDEnumerator – These methods can perform filtering and can be passed parameters, the idea is that it returns an ID (and a timestamp if possible), as it’s name implies it is used to enumerate all the ID’s (or primary keys). In the context of the profile import, if your key is say, an Active Directory email address, then the IDEnumerator should return the email address field.

Specific Finder – This method accepts an ID and returns just the information related to that ID. You’ll probably create a number of these using different filter descriptors.

 

Now that you have an idea of what the methods are and how they operate you can design web services that are low friction for the BDC.

You’ll need a web service that returns a list of ID’s (for the IDEnumerator), you’ll need a second webservice that accepts the same ID’s that were returned by the first method, this second method will comprise your Specific Finder methods.

Also don’t create trouble for yourself by building webservices that accept a large number of parameters, you’ll regret it, just keep it simple. The guys that developed the fantastic BDC Meta-Man product also have the same advice.

 

I’ve provided the SQL create statement and ADF file here.

Tuesday, October 14, 2008 9:05:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
BDC | code | MOSS
# Sunday, October 05, 2008

MOSS 2007 has the option to use a dedicated web front end server for crawling content:

Dedicatedcrawler

 

Why would you want to do this:

 

Advantages:

  • Search doesn’t compete with the end users – Large environments that need to crawl constantly can cause more traffic than normal user load, you don’t really want your users to experience slow pages just because your doing an index? By using a dedicated web front end that isn’t part of the load balanced cluster, your indexing won’t impact your users as much (I say as much, because you still need to think about the impacts of the database server).
  • Easy to move the WFE into the load balanced cluster – It’s a rather crude disaster recovery method, but it’s not that hard to move this box into the load balanced cluster if you really need the extra capacity or if one of your other servers fail. After all it’s just a normal web front end, but one that is reserved for the indexer.
  • Perfect place to run a backup central admin – You should always try to have more than one server running central admin (on a large farm anyway), that way if your main central admin server goes down, you still have a way to manage the farm.

 

Disadvantages:

  • More hardware – The obvious disadvantage to having an extra machine is the requirement of more hardware, which also means:
  • More cost – New hardware is an additional cost, but now that you have an idea of the advantages it brings, you can make a more informed decision.

 

 Joel has some other tips such as adding a robots.txt to servers that you don’t wish to participate in the indexing process.

Sunday, October 05, 2008 7:24:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
configuration | MOSS | Sharepoint | Search
# Friday, October 03, 2008

The next integration method that I want to discuss is the collection of data. As a web developer you’ve no doubt been asked to create a form that does X a number of times, you might even have created some tools to aide in this, it’s pretty common. So now that you’ve moved across to the dark side of MOSS development we need to break your thinking, if you want to create a form, first have a look at Microsoft InfoPath, InfoPath files can be published to MOSS running the InfoPath Forms Service.

 

The benefits to this approach are:

  • Less development – I won’t go as far to say that even the worst coder on your team could use this tool, that guy can’t do anything, but for simple stuff it’s not too hard to get your head around.
  • Central management of forms – You can easily keep track of your forms, how many times have you written a form that has already been developed? But seriously because each form is a content type, you can manage the metadata of a form from a single location, regardless of how many libraries you’ve added the content type to.
  • Can easily use existing MOSS features - This is the biggest feature, you can easily add workflow capabilities or publish your results to a list, I bet most of your forms currently just send an email and get written to a database.

The disadvantages are:

  • It requires a different way of thinking - This might not be a bad thing, but some large organisations have teams of programmers who spend 90% of their time doing some sort of forms based programming. It might be a hard sell for these guys.
  • Anonymous access might cause some issues – I’ve seen a few issues around anonymous access to forms, it’s by no means a show stopper, but you should think about this before you deploy.

I think the benefits far out weigh the disadvantages, in any case hopefully the next time you hear a developer say they’ll create a web part for a form, you’ll think about it a little more carefully.

Friday, October 03, 2008 4:04:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
Integration | MOSS | Sharepoint
# Wednesday, September 24, 2008

Just a quick tip, if your crawling external sources with the MOSS (or Enterprise Search), you might find that your crawl doesn’t finish or hangs, it might be worthwhile checking to see if the site you are crawling has a calendar with links:

 

image

 

You will need to determine the URL and then add a crawl rule to exclude that path, the crawler will see an infinite number of pages (it thinks each date and next link is a separate page).

Wednesday, September 24, 2008 1:17:00 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
MOSS | Search | Sharepoint
# Tuesday, September 23, 2008

I recently had a project where I needed to bind some business objects to a SPGridView in a web part, which on its own is trivial, but I also needed to create the columns for the SPGridView on the fly, since a number of business objects could be rendered in this control.

The main problem was the naming of these columns, if I just bound them directly to the Grid, I would get the ugly naming of my properties (I say it’s ugly, it’s actually pretty awesome if your a developer, but those pesky end users like pretty names).

The sample grid looks like this by default:

 

grid-without

 

So what I came up with isn’t exactly new or innovative, I created a custom attribute called Alias:

 

    public class AliasAttribute : System.Attribute
    {
        protected string[] aliasName;

        public AliasAttribute(params string[] alias)
        {
            this.aliasName = alias;
        }

        public string[] Alias
        {
            get { return aliasName; }
            set { aliasName = value; }
        }
    }

 

This attribute gets applied to each property on your business object with a pretty name:

 

        [Alias("Internet URL")]
        public string InternetAddress
        {
            get { return internetAddress; }
            set { internetAddress = value; }
        }

 

Now the code that performs the binding looks for properties with a custom attribute and pulls out the alias as the HeaderText:

BoundField newBoundField = new BoundField();
newBoundField.HeaderText = Helpers.GetPropertyAlias(field.Trim());
newBoundField.DataField = field.Trim();
grid.Columns.Add(newBoundField);
public static string GetPropertyAlias(string propertyName)
{
    //Change the next line for your business object ..
    PropertyInfo[] propInfos = typeof(BusinessObject).GetProperties();

    foreach (PropertyInfo propInfo in propInfos)
    {
        if (propInfo.Name.ToUpper().Equals(propertyName.ToUpper()))
        {
            object[] attribs = propInfo.GetCustomAttributes(typeof(AliasAttribute), true);
            foreach (object attrib in attribs)
            {
                if (attrib is AliasAttribute)
                {
                    AliasAttribute alias = (AliasAttribute)attrib;
                    //return the alias
                    return alias.Alias[0];
                }
            }
        }
    }

    //if we don't find the custom attribute, just return the same value we passed in
    return propertyName;
}

 

The end result is a nice pretty grid:

 

Grid-With

 

A sample project can be found here.

Tuesday, September 23, 2008 10:20:00 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0] - Trackback
code | MOSS | Sharepoint
Statistics
Total Posts: 191
This Year: 4
This Month: 0
This Week: 0
Comments: 41