Monday, June 29, 2015

View Search Results XML Data

One for future use to diagnose Search Results being returned:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xmp><xsl:copy-of select="*"/></xmp>
</xsl:template>
</xsl:stylesheet>

Wednesday, January 14, 2009

Setting List Item's Content Type even though Content Type is Read Only

I was taking over a project which did bulk imports of files into a SharePoint Document Library with specific "Content Types" for each document.

The guys who had done this developed a console application which would read an xml file and churn through it uploading the file and reading the Content Type and setting it as part of the file upload using this approach:

**Note removed non-important code

SPList list = web.Lists["List Name"];
SPFolder itemFolder = list.RootFolder;
SPListItem listItem;
SPFile file;

file = itemFolder.Files.Add(string.Concat(itemFolder.Url, "/", fileName), stream, true);
file.Update()

listItem = file.Item;

SPContentType contentType = list.ContentTypes["Name of Your Content Type"];

listItem["ContentTypeId"] = contentType.Id;
listItem.SystemUpdate();

**Note: We can't use the listItem.ContentType = contentType as the .ContentType property is read-only.

This seemed to do the trick and everything was all okay in SharePoint. I could see that the documents where updated with the correct Content Type and it all seemed to be pretty correct.

The issue came when the user's started to open the document in Word 2003 and when they clicked on "File Properties" it would always seem to default to the Document Library's default Content Type - not the Content Type in SharePoint - the one that we set during import.

This was unusual the CT was correct in SharePoint but the Word 2003 properties said otherwise, had a look at the properties in Word and sure enough the ContentTypeId was the correct one (one from SharePoint and migration), however the Content Type property was the default document library one instead. Weird!!!!

A few hours of digging around and found that I had to set the "ContentType" property as well not just the "ContentTypeId". Once this was done the File Properties document now knew which "Content Type" it was and populated the correct value now. Wowsers it all worked and with only one line of additional code:

listItem["Content Type"] = contentType.Name;

Couldn't believe that. I mean why make it Read Only when I can modify the property of the list item? Only issue now was to re-bulk upload 1593 documents JOY!!!!!

Thursday, July 31, 2008

Getting all values to debug xsl in Content Query Webpart

Just writing this because I always seem to be looking for it. This basically shows you what properties/fields and the values of these fields in the xsl when you are trying to render in a content query webpart or a dataview webpart.

<xsl:for-each select="@*">
Property Name: <xsl:value-of select="name()"/>
Value: <xsl:value-of select="." /> <br/>
</xsl:for-each>

Works like a charm. Thanks to everyone else who has blogged about this.

Monday, June 2, 2008

Creating Custom Advanced People Search in MOSS 2007

I was working on creating an advanced custom people search page which would provide the client with the ability to build a full text search query based on custom properties such as Skills, Role, Department etc, and came across a few approaches which were very interesting.

I firstly tried to create my query using the FullTextSqlQuery class which worked just as I wanted - this was awesome I could build my query using dropdown lists using a custom list as the datasource for properties like Role, Department etc. If you're interested in doing this see below for the code:

private const string SQLQUERYFORMATSTRING = "SELECT {0} FROM SCOPE() WHERE \"scope\"='{1}' {2} ORDER BY Rank DESC";
private const string RESULTFIELDS = "AccountName, PreferredName, JobTitle, Department, OfficeNumber, AboutMe, PictureURL, WorkEmail";
private const string SEARCHSCOPE = "People";

using (SPSite site = new SPSite(this.Page.Request.Url.ToString()))
{
// create a new FullTextSqlQuery class - use property intializers to set query
FullTextSqlQuery myQuery = new FullTextSqlQuery(site);
myQuery.QueryText = String.Format(SQLQUERYFORMATSTRING, RESULTFIELDS, SEARCHSCOPE, additionalWhereClause);
myQuery.ResultTypes = ResultType.RelevantResults;
myQuery.TrimDuplicates = true;
myQuery.EnableStemming = true;

// execute the query and load the results into a datatable
ResultTableCollection queryResults = myQuery.Execute();
ResultTable queryResultsTable = queryResults[ResultType.RelevantResults];
DataTable queryDataTable = new DataTable();
queryDataTable.Load(queryResultsTable, LoadOption.OverwriteChanges);
dgListResults.DataSource = queryDataTable;
dgListResults.DataBind();
}


Now the problem was how do I render my results? Obviously I could simply fix my Datagrid(dgListResults) to show a nicely formatted result. However I didn't want my results and the search to be on the same page.

I was told I had to use the OOB core results webpart and this was the big problem I now faced and after some digging I ran into Tom Clarkson's blog, and he describes using a custom advanced search box as a basis to pass the search query onto the core results webpart.

After further digging I found that I could basically build my fulltextsqlquery using this approach. I simply added properties as hidden fields and use the value of input fields as the criteria to search on. I could even run these hidden values on the server, as long as they had the correct IDs ie. (ASB_PS_plb_).

This was very very helpful I could simply point my dropdown list give it the correct ID and fill it with values from a list in SharePoint. The code looked like this:

private void BindDropDowns()
{
SPSite site = SPContext.Current.Site;
using (SPWeb web = site.RootWeb)
{

//BindDropDownList
BindDropDownList(web, ASB_PS_pvtb_2, dsRole, "Roles");
BindDropDownList(web, ASB_PS_pvtb_3, dsDepartment, "Business Units");
}
}

private void BindDropDownList(SPWeb web, DropDownList dropDownList, SPDataSource spDataSource, string listName)
{
spDataSource.List = web.Lists[listName];
dropDownList.DataSource = spDataSource;
dropDownList.DataBind();
}

<sharepoint:spdatasource runat="server" id="dsRole"></sharepoint:spdatasource>

<sharepoint:spdatasource runat="server" id="dsDepartment"></sharepoint:spdatasource>


<tr>
<td style="width:25%">
First name / preferred name<br />

<input name="ASB_TextDT_Props" type="hidden" value="AccountName#;#FirstName#;#PreferredName#;#LastName#;#JobTitle#;#Department"/>

<input id="ASB_PS_lolb_0" type="hidden" value="And" runat="server"/>
<input id="txtFirstName" type="text" runat="server" />
</td>
<td>
Last Name<br />
<input id="ASB_PS_plb_1" type="hidden" value="LastName" runat="server"/>
<input id="ASB_PS_olb_1" type="hidden" value="Contains" runat="server"/>
<input type="text" id="ASB_PS_pvtb_1" runat="server"/>
</td>
</tr>
<tr>
<td style="width:25%">
Role <br />
<input id="ASB_PS_plb_2" type="hidden" value="JobTitle" runat="server" />
<input id="ASB_PS_olb_2" type="hidden" value="Equals" runat="server" />
<asp:DropDownList id="ASB_PS_pvtb_2" runat="server" datatextfield="Title" datavaluefield="Title" AppendDataBoundItems="true">
<asp:ListItem Value="" Text=""></asp:ListItem>
</asp:DropDownList>
</td>
<td>
Business Unit / Group Function<br />
<input id="ASB_PS_plb_3" type="hidden" value="Department" runat="server" />
<input id="ASB_PS_olb_3" type="hidden" value="Equals" runat="server" />
<asp:DropDownList id="ASB_PS_pvtb_3" runat="server" datatextfield="Title" datavaluefield="Title" AppendDataBoundItems="true">
<asp:ListItem Value="" Text=""></asp:ListItem>
</asp:DropDownList>
</td>
</tr>
<tr>
<td><asp:Button runat="server" text="Search" id="btnPerformSearch"></asp:Button>
</td>
</tr>


Now the only thing left to do is add a postbackUrl to the submit button:
this.btnPerformSearch.PostBackUrl = SPContext.Current.Web.Url + SEARCHRESULTSPAGE;

Just a thing to note ASB_PS_lolb_0 is a one off property value. If you look at the advanced search box it only ever uses And or Or not both, and not between properties :( So you cannot have different property operators between each property, a drawback which unfortunately I needed to try and get around.

After several misguided attempts I ran into the following hidden property: ASB_ResType_Query - which is basically the Result Type query. I looked at this using the standard OOB and this is what it sets it to either one of these values from the array:

arrResTypeQuery[1] = 'FileExtension=\u0027htm\u0027 Or FileExtension=\u0027html\u0027 Or FileExtension=\u0027asp\u0027 Or FileExtension=\u0027aspx\u0027 Or FileExtension=\u0027cgi\u0027 Or FileExtension=\u0027php\u0027';arrResTypeQuery[2] = 'FileExtension=\u0027doc\u0027 Or FileExtension=\u0027docx\u0027 Or FileExtension=\u0027dot\u0027';arrResTypeQuery[3] = 'FileExtension=\u0027xls\u0027 Or FileExtension=\u0027xlsx\u0027 Or FileExtension=\u0027xlt\u0027';arrResTypeQuery[4] = 'FileExtension=\u0027ppt\u0027 Or FileExtension=\u0027pps\u0027 Or FileExtension=\u0027pot\u0027';arrResTypeQuery[5] = 'FileExtension=\u0027pdf\u0027';

So it would seem if you used ASB_ResType_Query to pass on your query it would then append it to the query used specified with the properties. So why not just pass in the whole fulltextsqlquery onto this property as a hidden value? Only problem with that is it doesn't get appended and a fulltextsqlquery isn't run unless there is at least one property search (ASB_PS_plb_) or if the scope is specified (ASB_SS_scb__) which is what I ended up using.

Now knowing that I could simply pass on my fulltextsqlquery on to the ASB_ResType_Query and set the scope and do the search - we ended up just building the query using javascript and also added a property for the page to find the scopeidnumber for the "People" search scope:


protected int PeopleScopeId
{
get
{
//Set Scope Hidden value field to search on people scope
SearchContext context = SearchContext.GetContext(SPContext.Current.Site);
Scopes scopeMgr = new Scopes(context);
Scope scope = scopeMgr.GetSharedScope("People");
return scope.ID;
}
}


protected override void OnLoad(EventArgs e)
{
if (!Page.IsPostBack)
{
BindDropDowns();
}
this.btnPerformSearch.PostBackUrl = SPContext.Current.Web.Url + SEARCHRESULTSPAGE;
this.btnPerformSearch.OnClientClick = "BuildQuery()";
base.OnLoad(e);
}


<script type="text/javascript">
var firstNameQuery = "";
function SetFirstNameQuery()
{
var txtFirstName = document.getElementById("<%=txtFirstName.ClientID %>");
if(txtFirstName)
{
if(txtFirstName.value != "")
{
firstNameQuery = "FirstName LIKE '%" + txtFirstName.value + "%' OR PreferredName LIKE '%" + txtFirstName.value + "%'";
}
}
}

function BuildQuery()
{
//Create First Name/ Preferred Name Query
SetFirstNameQuery();

if(firstNameQuery != "")
{
finalQuery = firstNameQuery;
}

element = document.getElementById("ASB_ResType_Query");
if(element)
{
element.value = finalQuery;
}
//alert("Query is: " + element.value)
}
</script>

<!--Additional Result Type Query - additional search conditions --><input name="ASB_ResType_Query" id="ASB_ResType_Query" type="hidden" /><!--Search on People Scope Id ---><input name="ASB_SS_scb_0_<%=PeopleScopeId%>" type="hidden" />




Well there we have it and here is a screenshot of it all coming together bar the design:



Obviously this approach could be incorporated into a custom advanced search box as well without having to mess around with your own custom xsl and results rendering and can simply point it to the OOB search results page or just a page with the Core Results Webpart or the People Core Results webpart.

Thursday, April 17, 2008

Rearranging SharePoint Fields

Ever wanted to rearrange those annoying fields in EditForm.aspx and NewForm.aspx, without having to modify the actual pages in SharePoint Designer (SPD)? If you did want to use SPD for this please see the following blog which explains very nicely how to do this (http://kalsing.blogspot.com/2006/11/create-custom-list-form-for-sharepoint.html). However there is a drawback to this approach as it is a post deploy approach - the site and list has to be already created - what happens when you need to include these field arrangements as part of a site template?

First of all lets start with the way you deploy your site columns (fields) and content types. Currently we deploy content types and site columns via features. There are lots of blogs out there and even just diving into the HIVE you can see how Microsoft does this out-of-the-box, so I won't go through this in detail (see C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES\fields and C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES\ctypes).

Firstly you define your fields:

<Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"
Name="Title"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="Title"
Group="_Hidden"
Type="Text"
DisplayName="$Resources:core,Title;"
Required="TRUE"
FromBaseType="TRUE">
</Field>

<Field id="{fa564e0f-0c70-4ab9-b863-0177e6ddd249}"
Name="FirstTitle"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="FirstTitle"
Group="_Hidden"
Type="Text"
DisplayName="First Title"
Required="TRUE">
</Field>

Now you define your content type:

<ContentType ID="0x01"
Name="$Resources:Item"
Group="$Resources:List_Content_Types"
Description="$Resources:ItemCTDesc"
Version="0">
<FieldRefs>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE"/>
</FieldRefs>
</ContentType>

<ContentType ID="0x0101" Name="Child Content Type" Group="$Resources:List_Content_Types"
Description="Child Content Type"
Version="0">
<FieldRefs>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd249}"
Name="FirstTitle" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE"/>
</FieldRefs>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd250}"
Name="SecondTitle" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE"/>
</FieldRefs>
</ContentType>

Now if you bind the Child Content Type to say the Pages library. The fields for the EditForm.aspx and NewForm.aspx will be displayed in the following order:

Title
First Title
Second Title

How about if you want First Title to come "First"?

By simply changing the order of the fields you put inside your content types it will change the order of the way they appear in the EditForm.aspx and NewForm.aspx. So if you referenced Second Title first in the content type the order would change to:

Title
Second Title
First Title

That's pretty neat! However what if I wanted "First Title" to go on top of the "Title" field?

When your content type inherits from the base content types or even content types you've defined then the order in your EditForm.aspx and NewForm.aspx will always show the parents fields first followed by the childs fields.

How to get around this problem? Simple all you need to do is add the same inherited field into your child content type and arrange it the way you want, this includes the base hidden site columns from the OOB content types. You then would obviously need to bind your content type to the list you want and this should do the trick. So if I wanted to put the fields in the following order:

First Title
Title
Second Title

Then I would simply just reference the Title field in the child content type:
<ContentType ID="0x0101" Name="Child Content Type" Group="$Resources:List_Content_Types"
Description="Child Content Type"
Version="0">
<FieldRefs>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd249}"
Name="FirstTitle" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE"/>
</FieldRefs>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE"/>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd250}"
Name="SecondTitle" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE"/>
</FieldRefs>
</ContentType>

This doesn't seem to break the inheritance of the content types either - just the ordering.

Apologies for the crappy cut and paste job - someone needs to teach me how to cut and paste properly on this.

First Up Blog

My first ever posting - I already now feel a little more nerdy :S

Actually it's about Bloggin time that I finally got off my lazy butt and started bloggin all the things I've learnt and use as a reference just in case I need to do the same thing again, plus I could always just tell people oh I've done that before you should go read my "Blog" how nerdy eh?

And so the bloggin now begins....