Friday, December 19, 2014

SharePoint 2013 JavaScript Copy List Item

I have a SharePoint 2013 custom list based form with 160 fields where there is a need to template list items and copy those items as new items.  I could have used SSOM since my solution is for an on-premise implementation but I prefer to use JSOM especially since my client wants the ability to move this solution to the O365 in the future.  MS documentation was helpful on using JSOM to manipulate list items but of course it didn't get into copying items.  I couldn't find any posts specific for the copy list item topic from others other than some related clues on stackoverflow.  The SSOM technique for copying list items was the starting point for this solution and was helpful but it wasn't as easy as finding an alternative JSOM object to the SSOM object.  So through a days worth of hit and miss and a lot of debugging, here is my JSOM code to copy a SharePoint 2013 list item.  I created a custom JSLink view of this list where I override the "Title" field with a "New from Template" button.


//Called from JSLink field override on the Title field, add new request button which will copy this template list item
function GetTemplateLink(ctx) {
    var ret;
    //get id of this item
    var srfID = ctx.CurrentItem.ID;
    ret = TemplateLink(srfID);
    return ret;
}
function TemplateLink(item_id) {
    //when user clicks button, copy of this item will be created and open in a new window in Edit mode
    return "<input type='button' value='New Request' onclick='jsNewFromTemplate(" + item_id.toString() + ",true);' title='New Request Form' class='clsFormButton' style='width:80px;'/> ";
}

var tempctx, tempItem, fieldCollection, tempList;

//load the list, the field collection for this list, and the item
function jsNewFromTemplate(id, isTemplate) {
    tempctx = SP.ClientContext.get_current();
    this.web = tempctx.get_web();
    tempctx.load(this.web);
    tempList = web.get_lists().getByTitle('Study Request');
    tempctx.load(tempList);
    fieldCollection = tempList.get_fields();
    tempctx.load(fieldCollection);
    tempItem = tempList.getItemById(id);
    tempctx.load(tempItem);
    tempctx.executeQueryAsync(Function.createDelegate(this, this.createListItem), Function.createDelegate(this, this.createListItemFail));
}
//create a new item and load it
function createListItem(sender, args) {
    var itemCreateInfo = new SP.ListItemCreationInformation();
    this.oListItem = tempList.addItem(itemCreateInfo);
    oListItem.update();
    tempctx.load(oListItem);
    tempctx.executeQueryAsync(Function.createDelegate(this, this.copyListItem), Function.createDelegate(this, this.createListItemFail))
}
//copy field values from original item into the new item
function copyListItem(sender, args) {
    this.newListItem = tempList.getItemById(oListItem.get_id());
    //customize title with unique ID
    newListItem.set_item('Title', '8' + oListItem.get_id());
    newListItem.update();
    //enumerate the list fields and update values
    var fieldEnumerator = fieldCollection.getEnumerator();
    while (fieldEnumerator.moveNext()) {
        var oField = fieldEnumerator.get_current();
        //exclude fields
        if ((!oField.get_readOnlyField())
            && (oField.get_internalName() != "Attachments")
            && (!oField.get_hidden())
            && (oField.get_internalName() != "Title")
            && (oField.get_internalName() != "ContentType"))
        {
            //get value of source field
            var sourceVal = tempItem.get_item(oField.get_internalName());
            if (sourceVal != null)
            {
                //set value on new item
                newListItem.set_item(oField.get_internalName(), sourceVal);
            }
        }
    }
    newListItem.update();
    tempctx.load(newListItem);
    tempctx.executeQueryAsync(Function.createDelegate(this, this.showForm), Function.createDelegate(this, this.createListItemFail))
}
//open copied item in editform.aspx
function showForm(sender, args) {
    var newsrfID = newListItem.get_id();
    var win = window.open(CreateFormUrl(tempctx, newsrfID), '', 'left=10,top=10,width=1000,height=800,toolbar=1,resizable=1,scrollbars=1,status=1');
    win.focus();
}
function createListItemFail(sender, args) {
    alert('Request failed. ' + args.get_message() +
        '\n' + args.get_stackTrace());
}
//function to get edit form
function CreateFormUrl(ctx, linkID) {
    var titleUrl = [];
    titleUrl.push(ctx.editFormUrl);
    titleUrl.push("&ID=");
    titleUrl.push(linkID);
    titleUrl.push("&isdlg=1");
    return titleUrl.join('');
}

Wednesday, January 15, 2014

SharePoint 2013 On-Premise or Office 365 and SharePoint Online?

I’ve worked with clients who struggle with the decision whether to implement SharePoint 2013 on-premise or move to the cloud-based Office 365 and SharePoint Online solution.

Administrative Tools and Controls

In the past, I was sure I’d always recommend on-premise mainly for the administrative control it provides but lately, when working with clients I’ve come around to recommending Office 365 and SharePoint Online mainly for the same reason I love on-premise…administrative control.  Administrative control is great if you have staff who understand and can support the potential complexity of a SharePoint 2013 on-premise implementation. But why deal with that administrative complexity and configuration when you can pay Microsoft to worry about that? From small businesses to large enterprises, I’ve heard worry in the voices of IT engineers and support staff when we dig into the design for SharePoint 2013 on-premise.

SharePoint Deployment for Small Organizations – Typical Approach

First let’s start with smaller organizations with less than 1000 users.   Typically, these organizations start with a grassroots IT implementation of SharePoint with a one or two server tiered deployment.  This is manageable initially but along the line management wants to increase adoption of SharePoint as a tool that can handle collaboration on documents very well, provide rich search, and integrate with other systems already running at the company.  With this desire for increased use, the IT staff know they need high availability, redundancy, and recoverability which you don’t get with a simple two server deployment.   This is when the IT staff, who is able to support the simple implementation, knows they need help and call on consultants.

SharePoint Deployment for Small Organizations – Enhanced Database Management

The consultants come in and define the business requirements and start getting into a design for Load Balanced Web Front Ends, Clustered or Mirrored SQL Servers, an application server tier with redundancy for Search, and a logical design with multiple web applications and site collections with defined quotas for better managing content databases – and the IT staff sit there with the deer-in-the-headlights look wondering how they’re going to be able to support this increased complexity. Then the consultants pile on designs for recovery, scaling the environment (considering how they want to scale workflow, distributed cache, office web apps server, BI elements including SQL Server Reporting Services and SQL Server Analysis Services, and Branch cache servers for better WAN performance…because management has these requirements), and patching all these new servers with daily maintenance which in some cases is going to need more tools (i.e., more cash) and definitely more staff to support (i.e., more cash).

Better Administration and Performance – Office 365 and SharePoint Online

At this point the IT Staff are ready to jump off the roof.   Oh, and management wants external access for employees and partners so the consultants get into the design for external access with talk of DMZ, SSL, remote proxy servers, SharePoint Antivirus, and federated authentication.  By this time, the IT staff are in such terror of the administration nightmare ahead that have jumped off the roof...actually, they’re saying to the consultants, “So tell us more about this Office 365 and SharePoint Online.”

SharePoint 2013 vs. SharePoint 2007 or 2010 – Understanding Deployment Complexities

Second, there is every organization with more than 1,000 users and ditto to everything for small organizations but in this case, the IT staff is already supporting a complex SharePoint 2007 or 2010 deployment (or both) with terabytes of content… which they now have to migrate and / or upgrade… but it turns out that SharePoint 2013 has tripled the complexity and scale where in the previous versions they were just supporting everything contained in SharePoint now they’re introduced to supporting a separate infrastructure for Workflow (i.e., Workflow Manager 1.0), Distributed Cache, Enterprise Search (Formerly Fast Search for SharePoint), and Office Web App Servers – it was hard enough patching SharePoint servers and now they’ve got to patch and scale more servers.  And remember the migration/upgrade... which could take months... migration / upgrade equals supporting 2 (or 3) versions of SharePoint until everyone’s off the old version(s).

Migration and Customization – on-premise, hybrid on-premise or cloud implementation

Oh, and then there are all the customizations that the larger organizations have done. Now the IT staff has to figure out how that’s going to work.  To be fair, they’ve got to figure out how to do the migration and customizations with Office 365 and SharePoint Online too where the complexity is most likely harder and may be the reason that larger organizations stick with an on-premise implementation and maybe consider a hybrid on-premise/cloud implementation.  Some of these large organizations have also let their implementations get out of hand so now they need to really bear down on a strong governance plan for the new SharePoint 2013 implementation with a desire for better SLAs where before maybe high availability at the server level was enough with a scheduled weekend outage once or twice a year for patching, now they need strategies where there is no downtime because some sites need to be up all the time so they need synchronized active/active server farms so they can have sites running live on a backup server farm while the production farm is being patched…of course some companies have been doing this with prior versions of SharePoint but the point is, with SharePoint online, you pay Microsoft to deal with this complexity instead having facilities, hardware, processes, and staff of your own to deal with this.

Office 365 and SharePoint Deployment

You will also find in most cases that business usage requirements, information architectures, and logical designs fit in either an Office 365 and SharePoint Online deployment or an on-premise deployment. If you’re a company which has customized SharePoint with a lot of server side code, when you bring up SharePoint online to developers and application support, you’ll probably get push back where they have to get up-to-speed and may have to rebuild solutions with the new client side development and app models.

Share Pointe Online for better SLAs

Going back to my original thought, I love administrative control and most IT managers, engineers, and architects do but in the end SharePoint is a business solution and not an IT solution.  So decisions around SharePoint have to put the business first.  SharePoint online is a good business solution with SLAs often better then what an on-premise solution offers so the IT managers need to change their mindset when it comes to SharePoint online.   You still need people to administer SharePoint online but in most cases you can do it with less staff.  Also, think of the simplistic 80/20 rule where 80 percent of the use in SharePoint is for out of the box functionality and 20 percent is for custom solutions (which may be closer to 90/10 based on the organizations where I’ve worked).  SharePoint online is a perfect solution for the 80% use without the maintenance complexity.

Bottom-line – Cost Factor

I won’t get into the cost comparison in this post because it’s different for each organization where some organizations have existing data center infrastructure which can support the new on-premise implementation while others need to buy more hardware and software outside of the SharePoint servers but when I’ve done assessments in the past and looked at the costs and savings of on-premise versus online, it can be close.

Monday, January 16, 2012

Add “Change Item Order” to a Custom Link List

I built a SharePoint 2010 Visual Studio solution which included a custom list definition based on a custom content type inherited from the out-of-the-box Links content type.  The out-of-the-box links list contains a “Change Item Order” button in it’s ribbon bar which I needed for my solution (see image below).  I thought I’d get in my custom list by inheriting from the Links content type but that didn’t happen.

image

So I had to get the Change Item order back in my ribbon.  Looking deeper I found that there’s a hidden page called reorder.aspx in _layouts which, with the combination of your list GUID, brings you to a page where you can reorder items.  Try it – go into your list settings and change the page part of the URL from listedit.aspx to reorder.aspx (e.g. http://sitename/_layouts/reorder.aspx?List=[GUID]).

The links list appears to be the only list where this reordering impacts a list view.  You can reorder other lists or libraries using the _layouts/reorder.aspx?List=[GUID] page but it does nothing to change your list views.  Comparing the Links list view settings to other list view settings, you’ll see that you have an option in the Links list to “Allow users to order items” (screenshot below) which you don’t get with other types of lists. 

image

However, the reorder values do stick when you go back to this reorder page for any type of list so in theory you could use this in a CAML query using a hidden field named “order” (e.g. string qry = "<OrderBy><FieldRef Name='Order' /></OrderBy>";) if you were to build a custom content query webpart which needed a link to a sorting interface.

But for my solution, I’m inheriting from the links list content type so the sort works, I just don’t get the option in the ribbon bar.  Chris O’Brien gives a good overview of customizing the ribbon here (Adding ribbon items into existing tabs/groups (ribbon customization part 2).  With Chris’ guide and viewing the source on an OOB Links list view page, I was able to determine that I could get the XML I needed to display the “Change Item Order” in my ribbon by opening the CMDUI.XML and grab the button element XML for Ribbon.ListItem.Actions.ChangeItemOrder (below).

<Button
Id="Ribbon.ListItem.Actions.ChangeItemOrder"
Sequence="20"
Command="ChangeLinkOrder"
Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-192" Image16by16Left="-144"
Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png" Image32by32Top="-192" Image32by32Left="-288"
LabelText="$Resources:core,cui_ButChangeItemOrder;"
ToolTipTitle="$Resources:core,cui_ButChangeItemOrder;"
ToolTipDescription="$Resources:core,cui_STT_ButChangeItemOrder;"
TemplateAlias="o1"/>


Here’s the final XML I used for the element I added to my visual studio solution to get the “Change Item Order” button to appear in my custom link list.  Highlighted in below example:

-- Registrationid=”30099” is my custom list definition's Type value.


-- Location="Ribbon.ListItem.Actions.Controls._children is the location where I want to place my button in the ribbon.


-- I changed the Id for the button from Ribbon.ListItem.Actions.ChangeItemOrder to Ribbon.ListItem.Actions.RibbonSortOrderButton (when viewing source on initial deployment of my custom list, I saw ChangeItemOrder was being trimmed by an OOB JavaScript function so by changing this Id I’m ensuring the OOB JavaScript isn’t trimming it)


-- Sequence="25" is my custom sequence for the order in which this button appears (see Chris O’Brien’s blog for more info).


-- Command="ChangeLinkOrder" is the OOB command which means I don’t have to add a CommandUIHandler.



<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<!--Change Item order Ribbon-->
<CustomAction
Id="XYZ.Webpart.Links.XYZLinksListDefinition.RibbonSortOrderButton"
Location="CommandUI.Ribbon"
RegistrationId="30099"
RegistrationType="List"
Title="List View Ribbon Customization" >
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition Location="Ribbon.ListItem.Actions.Controls._children">
<Button
Id="Ribbon.ListItem.Actions.RibbonSortOrderButton"
Sequence="25"
Command="ChangeLinkOrder"
Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-192" Image16by16Left="-144"
Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png" Image32by32Top="-192" Image32by32Left="-288"
LabelText="$Resources:core,cui_ButChangeItemOrder;"
ToolTipTitle="$Resources:core,cui_ButChangeItemOrder;"
ToolTipDescription="$Resources:core,cui_STT_ButChangeItemOrder;"
TemplateAlias="o1" />
</CommandUIDefinition>
</CommandUIDefinitions>
</CommandUIExtension>
</CustomAction>
</Elements>


Friday, January 6, 2012

SharePoint 2010 Validate Special Characters

I have a colleague who re-built my custom list based site provisioning solution using a Visual Studio Workflow where I created it using custom SharePoint Designer Workflow actions.  The solution uses the value of what an end-user puts in the “Title” field of the form to generate a URL.  Because the URL can’t contain Special Characters (i.e. ~!@#$%^&*()-+=[]';,./{}\":<>?"), I have actions which validate the column in my solution, using regular expressions, where he didn’t add that to his VS workflow redo.

My colleague left the project recently and not soon after I was alerted about an issue where the site provisioning failed for a user who put a colon and a comma in their Title – to workaround, we simply we had the user resubmit the form with instruction not to use the colon and comma in the Title field but I figured I could fix a reoccurrence by adding the validation on submission.  I didn’t want to jump into visual studio to update his form and workflow and then redeploy the whole thing so I thought “Simple solution…I’ll just add validation to the Title column in the list settings to check for special characters.”  I thought I would have found the formula out there already but had no luck so I’m putting it out now.

So here’s a formula to check for special characters in a text column which you can add via List Settings to the Validation Settings (Figure 1) for the entire list or just to the column’s Column Validation (Figure 2)

=AND(IF(ISERROR(FIND(",",Title)),TRUE),IF(ISERROR(FIND("&",Title)),TRUE),IF(ISERROR(FIND("!",Title)),TRUE),IF(ISERROR(FIND("@",Title)),TRUE),IF(ISERROR(FIND("~",Title)),TRUE),IF(ISERROR(FIND("#",Title)),TRUE),IF(ISERROR(FIND("$",Title)),TRUE),IF(ISERROR(FIND("%",Title)),TRUE),IF(ISERROR(FIND("^",Title)),TRUE),IF(ISERROR(FIND("*",Title)),TRUE),IF(ISERROR(FIND("(",Title)),TRUE),IF(ISERROR(FIND(")",Title)),TRUE),IF(ISERROR(FIND("-",Title)),TRUE),IF(ISERROR(FIND("=",Title)),TRUE),IF(ISERROR(FIND("+",Title)),TRUE),IF(ISERROR(FIND(":",Title)),TRUE),IF(ISERROR(FIND(";",Title)),TRUE),IF(ISERROR(FIND("<",Title)),TRUE),IF(ISERROR(FIND(">",Title)),TRUE),IF(ISERROR(FIND("?",Title)),TRUE),IF(ISERROR(FIND("'",Title)),TRUE),IF(ISERROR(FIND("{",Title)),TRUE),IF(ISERROR(FIND("}",Title)),TRUE),IF(ISERROR(FIND("[",Title)),TRUE),IF(ISERROR(FIND("]",Title)),TRUE),IF(ISERROR(FIND(".",Title)),TRUE),IF(ISERROR(FIND("/",Title)),TRUE),IF(ISERROR(FIND("\",Title)),TRUE),IF(ISERROR(FIND("""",Title)),TRUE))

NOTE:  To validate double quotes, you need to enter double double quotes in your formula (highlighted above).  This sample formula is validating a single word field (i.e., Title) so you don’t need to enclose the field name in brackets ([ ])  where if the field name contains two words you do need the brackets (e.g. [Project Name]).  Also, in SharePoint 2010, the validation formulas can be very similar to data validation formulas used in the past for Microsoft Excel so if your struggling to find a decent sample for a formula search for Microsoft Excel formula examples.

List Settings Validation Options Screenshots:

image

Figure 1 (For overall list – using this method, the validation message appears at top of list item edit form upon failure).

image

Figure 2 (For only the column – using this method, validation message appears below the field in the list item edit form upon failure).

Friday, December 30, 2011

SharePoint 2010 Search Scope to Aggregate Content

For scalability in SharePoint, I’m in favor of provisioning site collections instead of sub sites.  However, the challenge with Site Collections is that they are isolated from each other so you need to create custom solutions to aggregate content from across site collections.  Using SharePoint Search Scopes and the Search Core Results web part provides a way where an administrator could create an aggregated solution.  I also like to use search query web parts because it takes SQL out of the query loop and by modifying the web part’s XSL you have options for formatting the way results display (SharePoint Designer helps).

I had a requirement where a client wanted to display a feed of recent updates across site collections in a web part on a top level site.  I previously implemented a site provisioning solution for them which uses a site template – the template contains a discussion board, an images library, an announcement list, a document library, and a calendar.  The feed had to display similar to the My Sites Newsfeed where the feed item gives brief description of what type of content has been added or updated (e.g., A new image has been added…, A new discussion has been posted…, etc.).

The first thing I did was to create a Search Scope in the Search Service Application.  Within the search scope, I added the following rules:

Scope Rule Type Web Address Behavior Comment
Web Address

https://sites.xyz.com/sites

Include Rule to only include sites under the web address (we’re using a managed path under which all the sites are provisioned – in sample “sites” is the managed path)

The below rules use the property “contentclass”.  Contentclass is a search administration property commonly used in SharePoint search scopes.  More information on the ContentClass property can readily be found by doing a web search.

Scope Rule Type Property Query Behavior Comment
Property Query

contentclass=STS_ListItem_DiscussionBoard

Include This brings back all of the discussion board items
Property Query

contentclass=STS_ListItem_PictureLibrary

Include This brings back all of the Picture Library items
Property Query

contentclass=STS_ListItem_Events

Include This brings back all of the Calendar list items
Property Query

contentclass=STS_ListItem_Announcements

Include This brings back all of the Announcements list items
Property Query

contentclass=STS_ListItem_DocumentLibrary

Include This brings back all of the Document Library items
Property Query

contentclass=STS_List_850

Exclude The site template uses the Publishing Features which includes a “Pages” library – we don’t want results from the Pages library, just Documents, Announcements, Images, Events, and Discussion Board items

With this scope created, the next thing to do is to add a Search Core Results web part to a page on your top level site and set the Fixed Keyword Query value to use this scope.  With the fixed query, the results will display in the web part whenever the page loads.  Also set the Cross-Web Part query ID to something other than “User Query” (in the sample below, it is set to “Query 3”).

image

My client also wanted the results to display in Newsfeed format.  To do this, I needed to switch the Search Core Results Display properties to set sorting to Modified Date, deselect Use Location Visualization where I add contentclass as a column to bring back in search results (I need this to create conditions in my XSL to determine what to display in the feed statement) and also added the column for the managed property “SiteTitle” which I use to display the name of the site where the update occurred.

image

Finally, using SharePoint designer, I was able to take the raw XML search results and generate the XSLT to display the results in feed format which I added via the XSL editor in the Search Core Results Web Part (see MSDN guidance here for customizing search results and Tobias Zimmergren has the step-by-step for SP 2007 here which isn’t much different in SP 2010).

SharePoint 2010 and the Usage BLOB

I had a client who wanted to show a list of all site collections and sort them by most popular using site hits.  I was able to use Martin Kearn’s ShrarePoint 2010 solution on Codeplex (here) to return the aggregated site collection list however Martin’s solution wasn’t grabbing site hits even though there is a method within his code that appears to do that – I confirmed that it doesn’t work.

I then came across the solution in the SharePoint Diaries Blog, in the post where Diego Bruno Galeota documents Parsing the SharePoint Usage BLOB and provides the code.  Site hits are collected if you have SharePoint’s Web Analytics enabled where the analytics is stored as a BLOB in the database – so to get site hits out of that BLOB, you have to parse it – which is what Diego’s code does.  I deployed Diego’s code and it works for SharePoint 2010.  However, one update I made to Diego’s code was to change the GetUsageBlob function in the UsageBlob.cs class to remove the NetworkCredential property (snippet below).  Martin’s SharePoint directory solution is running at the farm level using elevated credentials so it works without having to hard code credentials.

UsageBlob.cs Snippet
  1. //public static UsageBlob GetUsageBlob(string url, bool currentBlob, NetworkCredential credential)
  2. public static UsageBlob GetUsageBlob(string url, bool currentBlob)
  3. {
  4.     var uri = new Uri(url);            
  5.     
  6.     ....

Then, using Martin’s Site Directory Solution I was able to add the Site Hits to the Site Directory list with the following function:

ScanJobHelperMethods.cs Snip
  1. public int GetHitsCount(SPWeb webInSite)
  2. {
  3.     int intHits = 0;
  4.     GlobalMethods globalMethods = new GlobalMethods();
  5.     System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
  6.  
  7.     try
  8.     {
  9.         string thisUrl = webInSite.Url.ToString();
  10.         var blob = UsageBlob.GetUsageBlob(thisUrl, true);
  11.         var now = DateTime.Now;
  12.         var minDate = new DateTime(now.Year, now.Month, now.Day);
  13.         var browsers = from b in blob.Browsers.UsageRecords
  14.                         select new
  15.                         {
  16.                             Name = b.Key,
  17.                             Hits = (
  18.                                     from v in b.Values
  19.                                     where v.Date <= minDate
  20.                                     select v.Count
  21.                                     ).Sum(),
  22.                             Percentage = 0
  23.                         };
  24.  
  25.         var totalHits = browsers.Sum(b => b.Hits);
  26.         if (totalHits > 0)
  27.         {
  28.             browsers = from b in browsers
  29.                         orderby b.Hits descending
  30.                         select new
  31.                         {
  32.                             Name = b.Name,
  33.                             Hits = b.Hits,
  34.                             Percentage = (int)Math.Round(((double)b.Hits / totalHits) * 100)
  35.                         };
  36.         }
  37.         intHits = totalHits;
  38.     }
  39.     catch (Exception ex)
  40.     {
  41.         globalMethods.WriteULSEntry(ex, TraceSeverity.Unexpected, null);
  42.     }
  43.  
  44.     return intHits;  
  45. }

Thursday, December 29, 2011

Adding XSL to a SharePoint Visual Studio Solution

When working with custom SharePoint search results web parts, inheriting from CoreResultsWebpart you’ll often use XSL to transform the search results display (this is not limited to this type of web part, you can use for list views too).  You could deploy the web part and configure the XSL after deployment or you could include the link to your XSL in your web part and package the XSL with your Visual Studio 2010 project.  To do the latter, from your Visual Studio Project, right click the name of the project in the Solution Explorer pane, select Add, and select SharePoint Mapped Folder.

image

In resulting window, select the folder at path Template\LAYOUTS\XSL to map.  Once mapped, I suggest adding a custom folder so it’s clear that this is custom XSL and then either upload your existing *.xsl file or create a new one and add your XSL (Side Note, SharePoint Designer does a great job of generating XSL based on any sample XML).

image

In the inherited class’ ConfigureDataSourceProperties set the XslLink property to the path of the XSL in the hive (“/_layouts/xsl/xyzcustom” in this case).

protected override void ConfigureDataSourceProperties()
{
...
this.XslLink = "/_layouts/xsl/xyzcustom/mmsearch.xsl";

....

Now, wherever you deploy the web part and add to a page, it will use your custom XSL.

Using this method, the XSL can not be overridden, If you want to overide after adding the web part, instead add to your web part's properties like this.



<?xml version="1.0" encoding="utf-8"?>
<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="XYZSearchBuilder, $SharePoint.Project.AssemblyFullName$" />
<importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage>
</metaData>
<data>
<properties>
<property name="Title" type="string">My Core Search Results</property>
<property name="Description" type="string">Inherits from Core Results Web Part. This web part provides a fixed query builder configuration to set keyword values to provide results containg the currently logged in user. The web part also provides configuration to sort results by a managed property (reduce storage may need to be set).</property>
<property name="XslLink" type="string">/_layouts/xsl/xyzcustom/xyzsearch.xsl</property>
</properties>
</data>
</webPart>
</webParts>