Friday, November 11, 2011

SharePoint 2010: Programmatically send a file to the records center

If you want to send a file to a Records Center, whether it's a sub site or a seperate site collection, you can use the following code:

string additionalInformation = string.Empty;

SPOfficialFileHost ofh = new SPOfficialFileHost();
ofh.Action = SPOfficialFileAction.Move;
ofh.OfficialFileName = item.File.Name;
ofh.OfficialFileUrl = new Uri("http://yourserver/sites/RecordsCenter");

OfficialFileResult returnValue = item.File.SendToOfficialFile("Content-type", ofh, "", SPOfficialFileSubmissionMode.ExpirationPolicy, out additionalInformation);

if (returnValue != OfficialFileResult.Success)
{
throw new Exception("Error sending file to records center: " + additionalInformation);
}

Friday, October 28, 2011

How To: Programmatically generate a report and save the results as a PDF to SharePoint.

I had a requirement this week to run a report from within one of our WCF services, get it as a PDF file and then save it to a SharePoint library. To do this you have to use the reporting services ReportExecutionService to run the report and get the file, then use the SharePoint Copy service to store it in the library. Here's how:

1. Add a web reference to the service 
1.1 Right click on your project and click on Add Service Reference
 1.2 Click on the Advanced button in the bottom left
 1.3 Click on Add Web Reference
 1.4 In the URL box add the reference to
http://yourReportServer/reportserver/reportexecution2005.asmx?wsdl
 1.5 Give your service a name and click on Add Reference

2. Create the service call method

// create the service
ReportExecutionService rs = new ReportExecutionService();
rs.Credentials = EcmServices.CreateNetworkCredential();
rs.Url = ConfigurationSettings.AppSettings["ReportServerUrl"];


// Prepare report parameter.
ParameterValue[] parameters = new ParameterValue[1];
parameters[0] = new ParameterValue();
parameters[0].Name = "ID";
parameters[0].Value = id;  // this is a value I pass to the method


// render arguments
byte[] result = null;
string reportPath = "/My Reports/Report";
string format = "PDF";
string historyID = null;
string devInfo = @"<DeviceInfo><Toolbar>False</Toolbar></DeviceInfo>";
string encoding;
string mimeType;
string extension;
Warning[] warnings = null;
string[] streamIDs = null;


// create execution info
ExecutionInfo execInfo = new ExecutionInfo();
ExecutionHeader execHeader = new ExecutionHeader();
rs.ExecutionHeaderValue = execHeader;
execInfo = rs.LoadReport(reportPath, historyID);
rs.SetExecutionParameters(parameters, "en-us");
String SessionId = rs.ExecutionHeaderValue.ExecutionID;


// execute the report and get the rendered file as a byte array
result = rs.Render(format, devInfo, out extension, out encoding, out mimeType, out warnings, out streamIDs);
execInfo = rs.GetExecutionInfo();


if(result != null)
{
 SubmitFileToSharePoint(result, id);
}


As above, the results return as a byte[] and this can be used in the SharePoint Copy service to store the file.

3. Store the file using the SharePoint Copy service
You need to add a web reference to the SharePoint Copy service in the same way as you did for the ReportExecutionService. The URL for this service is: http://yourSharepointServer/_vti_bin/Copy.asmx
public static void SubmitFileToSharePoint(byte[] fileBytes, int id){
 // create the copy service
 Copy copyWS = new Copy();
 copyWS.Url = "
http://yourSharepointServer/_vti_bin/Copy.asmx";
 copyWS.Credentials = System.Net.NetworkCredential("username", "password", "domain");


 // create the file url
 string documentLibraryUrl = "
http://yourSharepointServer/Documents/Report_" + id + ".pdf";
 string[] destinationUrls = { documentLibraryUrl };


 // set metadata values on the file
 FieldInformation info1 = new FieldInformation();
 info1.DisplayName = "Report ID";
 info1.Value = id.ToString();
 FieldInformation[] info = { info1 };


 // create the result variable
 var copyResult = new CopyResult();
 CopyResult[] copyResults = { copyResult };


 // copy the file
 copyWS.CopyIntoItems(documentLibraryUrl, destinationUrls, info, fileBytes, out copyResults);
 copyWS.Dispose();

 if (copyResults[0].ErrorCode != CopyErrorCode.Success)
 {
  throw new SPException(string.Format("Error copying file to {0}. {1}", copyResults[0].DestinationUrl, copyResults[0].ErrorMessage));
 }
}

Thursday, October 13, 2011

How To: Stretch an iFrame to the size of its contents

I had a requirement to put a PageViewerControl inside a SharePoint 2007 page (which is rendered out as an iFrame) and the height of it must be set to the height of its contents. Now most browsers won't allow you to do this at all for security reasons and I would suggest finding another way to build this, but during my development I did get this little script working on a lot of the more common browsers. Not sure how it will do on IE8 or IE9 though...

<script type="text/javascript">
 // get the iframe
 var oFrame = document.getElementsByTagName("iframe")[0];

 function SetFrameHeight_FireFox(){ 
  try{
   // remove border and scrolling
   oFrame.setAttribute("frameborder","0");
   oFrame.setAttribute("scrolling","no");
 
   // set height and width
   var oBody = oFrame.contentWindow.document.body;
   document.getElementById("ifrm").style.height = oBody.scrollHeight + "px";
   document.getElementsByTagName("iframe")[0].style.width = "960px";
  }
  catch(e){
   window.status =  'Error: ' + e.number + '; ' + e.description;
  }
 }

 function SetFrameHeight_IE(){
  try{
   // set height and width
   var oBody = oFrame.document.body;
   document.getElementById("ifrm").style.height = ifrm.document.body.scrollHeight + (ifrm.document.body.offsetHeight - ifrm.document.body.clientHeight);
   document.getElementsByTagName("iframe")[0].style.width = "960px";
  
   // remove scrolling
   ifrm.document.body.setAttribute('scroll', 'no');
  }
  catch(e){
   window.status =  'Error: ' + e.number + '; ' + e.description;
  }
 }

 // check for browser type and call function when frame has finished loading.
 if(document.implementation && document.implementation.createDocument)
 {
  oFrame.setAttribute("onLoad","SetFrameHeight_FireFox()");
 }
 else
 {
  oFrame.setAttribute("onload","SetFrameHeight_IE()");
 
 }
</script>

Thursday, October 6, 2011

How To: SharePoint 2010 Exchange Rate Web Part

This is another web part which our clients frequently require which is not always available on the web, and when it is it's not what you're looking for. The workings of this is going to be really simple so you will be able to customize it easily for your needs. The web part will for now only display selected exchange rates. The RSS feed is provided by TheMoneyConverter.com



1. Create a Visual Web Part project
In Visual Studio, click on File > New > Project and select the Visual Web Part template from the SharePoint 2010 installed templates. Give your project a name e.g. CurrencyWebPart, click Ok and click Finish.



In your solution explorer you can rename the web part and its code files to suit your needs. I have renamed mine to CurrencyWebPart. Remember to update the properties in the .webpart file if you rename your solution elements otherwise you will get an issue with the SafeControl entries when trying to add the web part to a page.

2. Add the front-end controls

The following is the code for the user control .ascx file
<style type="text/css">
    .erBox
    {
        text-align:center;
        width:300px;
    }
    .erTitle, erItem, erDate
    {
        font-size:8pt;
        font-family:Verdana, Arial, Helvetica, Sans-Serif;
        color:#676767;
    }
   
    .erTitle
    {
        font-weight:bold;
        margin-bottom:5px;
    }
   
    .erLastUpdate
    {
        margin-top:5px;
        font-weight:bold;
    }
   
    .erDate, .erLastUpdate
    {
        font-style:italic;
    }
</style>

<div class="erBox">
    <div class="erTitle"><asp:Label runat="server" ID="lblTitle"></asp:Label></div>
    <div class="erItem">1 USD = <asp:Label runat="server" ID="lblUSD"></asp:Label> ZAR</div>
    <div class="erItem">1 GBP = <asp:Label runat="server" ID="lblGBP"></asp:Label> ZAR</div>
    <div class="erItem">1 EUR = <asp:Label runat="server" ID="lblEUR"></asp:Label> ZAR</div>
    <div class="erLastUpdate">Last update on:</div>
    <div class="erDate"><asp:Label runat="server" ID="lblDate"></asp:Label></div>
</div>


3. Add the code for rendering the page

The following code will get the Xml document from the URL and transform the data for the web part.

public partial class CurrencyUserControl : UserControl
{
 public const string _rssUrl = "
http://themoneyconverter.com/ZAR/rss.xml";
 protected void Page_Load(object sender, EventArgs e)
 {
  // get the nodes
  XmlDocument xdoc = CreateXmlDoc();
  string title = xdoc.SelectSingleNode("/rss/channel/title").InnerText;
  string buildDate = xdoc.SelectSingleNode("/rss/channel/lastBuildDate").InnerText;
  XmlNodeList nodeList = xdoc.SelectNodes("/rss/channel/item");

  DisplayExchangeRates(nodeList);
  lblTitle.Text = title;
  lblDate.Text = buildDate;
 }

 private void DisplayExchangeRates(XmlNodeList nodeList)
 {
  foreach (XmlNode node in nodeList)
  {
   string title = GetTitle(node.SelectSingleNode("title").InnerText);
   double value = GetValue(node.SelectSingleNode("description").InnerText);

   if (title.Equals("USD"))
    lblUSD.Text = value.ToString("##.00");
   else if (title.Equals("GBP"))
    lblGBP.Text = value.ToString("##.00");
   else if(title.Equals("EUR"))
    lblEUR.Text = value.ToString("##.00");
  }
 }

 private string GetTitle(string title)
 {
  string val = title;
  val = val.Substring(0, val.IndexOf("/"));
  return val;
 }

 private Double GetValue(string value)
 {
  // seeing that the value is in a description we have to hack it out there... yes I know it's nasty
  string val = value;
  val = val.Substring(val.IndexOf("=") + 2);
  val = val.Substring(0, val.IndexOf(" "));
  return 1 / double.Parse(val);
 }

 private XmlDocument CreateXmlDoc()
 {
  // create a reader and populate the document
  XmlReader reader = XmlReader.Create(_rssUrl);
  XmlDocument doc = new XmlDocument();
  doc.Load(reader);
  return doc;
 }
}


4. Deploy the solution

The solution can be deployed by right clicking the project in your solution explorer and clicking Deploy.

Wednesday, October 5, 2011

How To: SharePoint 2010 News Scroller Web Part

In this tutorial I will show you how to create a dynamic news scroller that will take the top news headlines and make them scroll upwards after a short period of time. This was done with a SharePoint 2010 Visual Web Part and some JavaScript from DynamicDrive.com.


For the purpose of this tutorial I will be hard-coding a URL from News24.com and using their latest articles. If you want the web part to be more dynamic in letting users choose their region or a different news site you can look at adding custom properties for the web part which can be set later on. View my How To: SharePoint 2010 Weather Web Part blog article to find out more on how this is done.

1. Create a new Visual Web Part project

Click on File > New > Project and select the Visual Web Part project template from the SharePoint 2010 Templates and click Ok.
Deploying as a Farm solution should be your only option so just click on Finish.
(I have renamed my web part to NewsScrollerWebPart for this tutorial)


2. Add the JavaScript for the scroller

Right click your project and click Add > SharePoint Mapped Folder and choose the TEMPLATES/LAYOUTS folder. Add a new normal folder inside with the name of your web part e.g. NewsScrollerWebPart. Create a new JScript file inside and paste the following code into it:





/***********************************************
* Pausing up-down scroller- © Dynamic Drive (
www.dynamicdrive.com)
* Visit
http://www.dynamicdrive.com/ for this script and 100s more.
***********************************************/

function pausescroller(content, divId, delay) {
    this.content = content //message array content
    this.tickerid = divId //ID of ticker div to display information
    this.delay = delay //Delay between msg change, in miliseconds.
    this.mouseoverBol = 0 //Boolean to indicate whether mouse is currently over scroller (and pause it if it is)
    this.hiddendivpointer = 1 //index of message array for hidden div
    document.write('<div id="' + divId + '" style="position: relative; overflow: hidden"><div class="innerDiv" style="position: absolute; width: 100%" id="' + divId + '1">' + content[0] + '</div><div class="innerDiv" style="position: absolute; width: 100%; visibility: hidden" id="' + divId + '2">' + content[1] + '</div></div>')
    var scrollerinstance = this
    if (window.addEventListener) //run onload in DOM2 browsers
        window.addEventListener("load", function () { scrollerinstance.initialize() }, false)
    else if (window.attachEvent) //run onload in IE5.5+
        window.attachEvent("onload", function () { scrollerinstance.initialize() })
    else if (document.getElementById) //if legacy DOM browsers, just start scroller after 0.5 sec
        setTimeout(function () { scrollerinstance.initialize() }, 500)
}

// -------------------------------------------------------------------
// initialize()- Initialize scroller method.
// -Get div objects, set initial positions, start up down animation
// -------------------------------------------------------------------

pausescroller.prototype.initialize = function () {
    this.tickerdiv = document.getElementById(this.tickerid)
    this.visiblediv = document.getElementById(this.tickerid + "1")
    this.hiddendiv = document.getElementById(this.tickerid + "2")
    this.visibledivtop = parseInt(pausescroller.getCSSpadding(this.tickerdiv))
    //set width of inner DIVs to outer DIV's width minus padding (padding assumed to be top padding x 2)
    this.visiblediv.style.width = this.hiddendiv.style.width = this.tickerdiv.offsetWidth - (this.visibledivtop * 2) + "px"
    this.getinline(this.visiblediv, this.hiddendiv)
    this.hiddendiv.style.visibility = "visible"
    var scrollerinstance = this
    document.getElementById(this.tickerid).onmouseover = function () { scrollerinstance.mouseoverBol = 1 }
    document.getElementById(this.tickerid).onmouseout = function () { scrollerinstance.mouseoverBol = 0 }
    if (window.attachEvent) //Clean up loose references in IE
        window.attachEvent("onunload", function () { scrollerinstance.tickerdiv.onmouseover = scrollerinstance.tickerdiv.onmouseout = null })
    setTimeout(function () { scrollerinstance.animateup() }, this.delay)
}


// -------------------------------------------------------------------
// animateup()- Move the two inner divs of the scroller up and in sync
// -------------------------------------------------------------------

pausescroller.prototype.animateup = function () {
    var scrollerinstance = this
    if (parseInt(this.hiddendiv.style.top) > (this.visibledivtop + 5)) {
        this.visiblediv.style.top = parseInt(this.visiblediv.style.top) - 5 + "px"
        this.hiddendiv.style.top = parseInt(this.hiddendiv.style.top) - 5 + "px"
        setTimeout(function () { scrollerinstance.animateup() }, 50)
    }
    else {
        this.getinline(this.hiddendiv, this.visiblediv)
        this.swapdivs()
        setTimeout(function () { scrollerinstance.setmessage() }, this.delay)
    }
}

// -------------------------------------------------------------------
// swapdivs()- Swap between which is the visible and which is the hidden div
// -------------------------------------------------------------------

pausescroller.prototype.swapdivs = function () {
    var tempcontainer = this.visiblediv
    this.visiblediv = this.hiddendiv
    this.hiddendiv = tempcontainer
}

pausescroller.prototype.getinline = function (div1, div2) {
    div1.style.top = this.visibledivtop + "px"
    div2.style.top = Math.max(div1.parentNode.offsetHeight, div1.offsetHeight) + "px"
}

// -------------------------------------------------------------------
// setmessage()- Populate the hidden div with the next message before it's visible
// -------------------------------------------------------------------

pausescroller.prototype.setmessage = function () {
    var scrollerinstance = this
    if (this.mouseoverBol == 1) //if mouse is currently over scoller, do nothing (pause it)
        setTimeout(function () { scrollerinstance.setmessage() }, 100)
    else {
        var i = this.hiddendivpointer
        var ceiling = this.content.length
        this.hiddendivpointer = (i + 1 > ceiling - 1) ? 0 : i + 1
        this.hiddendiv.innerHTML = this.content[this.hiddendivpointer]
        this.animateup()
    }
}

pausescroller.getCSSpadding = function (tickerobj) { //get CSS padding value, if any
    if (tickerobj.currentStyle)
        return tickerobj.currentStyle["paddingTop"]
    else if (window.getComputedStyle) //if DOM2
        return window.getComputedStyle(tickerobj, "").getPropertyValue("padding-top")
    else
        return 0
}


3. Create the JavaScript Links from the RSS XML

The script file uses a javascript array of links to render. We will basically just pull out the XML from the RSS feed, select the title and link nodes for each news headline and build up html links for the array:


public partial class NewsScrollerUserControl : UserControl
{
 // url of the RSS feed
 public const string _rssUrl = "
http://feeds.news24.com/articles/News24/TopStories/rss";
 protected void Page_Load(object sender, EventArgs e)
 {
  XmlDocument xdoc = CreateXmlDoc();
  XmlNodeList nodeList = xdoc.SelectNodes("/rss/channel/item");
  string script = BuildScript(nodeList);

  // register the script on the page
  ClientScriptManager csm = Page.ClientScript;
  csm.RegisterClientScriptBlock(this.GetType(), "ScrollerScript", script, true);
 }

 private String BuildScript(XmlNodeList nodeList)
 {
  // build the script
  StringBuilder sb = new StringBuilder();
  sb.Append("var pausecontent = new Array();");

  // add a link for each news headline
  int count = 0;
  foreach (XmlNode node in nodeList)
  {
   sb.Append(String.Format("pausecontent[{0}] = '<a target=\"_blank\" href=\"{1}\">{2}</a>'; ", count, node.SelectSingleNode("link").InnerText, node.SelectSingleNode("title").InnerText));
   count++;
  }

  return sb.ToString();
 }

 private XmlDocument CreateXmlDoc()
 {
  // create a reader and populate the document
  XmlReader reader = XmlReader.Create(_rssUrl);
  XmlDocument doc = new XmlDocument();
  doc.Load(reader);
  return doc;
 }
}


4. Add the Styles, JavaScript call method and script reference
This call method will make a call with your rendered links to the JavaScript file and bring back the scrolling HTML.

<script src="/_layouts/NewsScrollerWebPart/Scroller.js" type="text/javascript"></script>
<style type="text/css">
    #pscroller{
        width: 100%;
        height: 15px;
        border: 1px solid #ededed;
        padding: 3px;
        font-family:Verdana, Arial, sans-serif;
        font-size:8pt;
    }

    #pscroller a{
        text-decoration: none;
        color:#676767;
    }
   
    #pscroller a:hover{
        text-decoration: underline;
        color:#676767;
    }
</style>

<script type="text/javascript">
    new pausescroller(pausecontent, "pscroller", 3500);
</script>

Monday, October 3, 2011

How To: SharePoint 2010 List Definition with Managed Metadata fields

I recently had a request to create some custom list definitions for a site definition, and within these list definitions were some fields that had to link up to the Taxonomy of the company.

This is not as simple as just adding the fields to the Schema, you also need to create an event receiver that will attach to the FeatureActivated event handler of your feature in order to connect the fields up to the taxonomy. This sounds like quite a complicated solution but even though it is a lot of work, it is in fact really simple. Here's how...


1. Create the list definition project

In Visual Studio, choose to create a new project and select the List Definition project type under the SharePoint 2010 templates:




Choose Deploy as Farm Solution and click Finish

2. Update the name of your definition

After the project has been created you will see that Visual Studio has not inserted the correct names for your list definition. You can update the following to ensure your list has the correct names. (Use your list name wherever I used "MyListDefinition")


Change the actual list definition name from "ListDefinition1" to your list name in the solution explorer



Open the Elements.xml file and change the DisplayName and Description properties. Note: Don't change the value of the Name property as the comment suggest because this may cause an issue with folder names not being correct.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="
http://schemas.microsoft.com/sharepoint/">
    <!-- Do not change the value of the Name attribute below. If it does not match the folder name of the List Definition project item, an error will occur when the project is run. -->
    <ListTemplate
        Name="MyListDefinition"
        Type="10000"
        BaseType="0"
        OnQuickLaunch="TRUE"
        SecurityBits="11"
        Sequence="320"
        DisplayName="My List Definition"
        Description="This creates a MyListDefinition list"
        Image="/_layouts/images/itann.png"/>
</Elements>


Open the Schema.xml file and change the Title, Url fields

<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint"
      Title="My List Definition"
      FolderCreation="FALSE"
      Direction="$Resources:Direction;"
      Url="Lists/My List Definition"
      BaseType="0"
      xmlns="
http://schemas.microsoft.com/sharepoint/">

3. Create a Content Type

Your managed metadata fields have to be inside a Content Type because it uses pre-defined system fields.

  1. Right click your project and click on Add > New Item...
  2. Select the Content Type template under the SharePoint 2010 templates, give it a name and click ok.
  3. Select "Item" as your base content type and click Finish. I usually pick "Item" as the base because it only contains the Title field which we will make a hidden field.
 
 
Open the Elements.xml file of your content type and update the Name, Group and Description properties.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="
http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Item (0x01) -->
  <ContentType ID="0x01002056f65066514e2e86ac6442eedd8ca8"
               Name="MyContentType"
               Group="MyContentTypes"
               Description="My Content Type"
               Inherits="TRUE"
               Version="0">
    <FieldRefs>
    </FieldRefs>
  </ContentType>
</Elements>


Inside the <FieldRefs> section of your content type, add the fields you are going to use in the list. These are just references to the fields we are still going to create, but it does not matter which ones you create first.

<FieldRefs>
  <FieldRef ID="{67051A64-C702-4FD6-A7BC-9EECFCCCC033}" Name="MyTextField"/>
  <FieldRef ID="{21E0A786-8F94-4A7D-98C3-A8B06B8C592A}" Name="MyUserField"/>
  <FieldRef ID="{B72AF39A-1F54-4041-93C2-F045CA8EDF04}" Name="MyTaxonomyField"/>
  <FieldRef ID="{6AAEB05A-2B38-482E-BC3A-FE2705E99709}" Name="MyTaxonomyFieldTaxHTField0"/>
 
  <!-- DO NOT CHANGE THESE NAMES OR IDS-->
  <FieldRef ID="{f3b0adf9-c1a2-4b02-920d-943fba4b3611}" Name="TaxCatchAll"/>
  <FieldRef ID="{8f6b6dd8-9357-4019-8172-966fcd502ed2}" Name="TaxCatchAllLabel"/>
</FieldRefs>


As you can see, for each ManagedMetadata field you need to add another <FieldRef> which will be used as a "Note" field for itself. This is used to store the actual data of the field as the Taxonomy one only makes a reference to the taxonomy.

You also need to add the bottom two fields called "TaxCatchAll" and "TaxCatchAllLabel". These are put in so that SharePoint search can find items of this content type. Note that since these two are system fields, you must NOT change the GUID's. You can use the standard Visual Studio GUID creator to generate the ID's for the rest of the fields found in Tools > Create GUID > Registry Format.

4. Add the Content Type and Fields to the List Schema

Delete everything in the <ContentTypes> section and add a reference to yours by copying the ID that was generated in the Elements.xml file of your content type.

<ContentTypes>
  <ContentTypeRef ID="0x01002056f65066514e2e86ac6442eedd8ca8" />
</ContentTypes>


Now, delete everything in the <Fields> section and add the references to the fields in the Elements.xml of your content type. We will start by adding the Title field and making it hidden. Then we will add field definitions for the rest:

<Fields>
  <!-- DO NOT CHANGE THE ID OF THE TITLE FIELD -->
  <Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"
   Type="Text"
   Name="Title"
   ShowInNewForm="FALSE"
   ShowInEditForm="FALSE"
   ShowInDisplayForm="FALSE"
   ShowInFileDlg="FALSE"
   DisplayName="$Resources:core,Title;"
   Sealed="TRUE"
   SourceID="
http://schemas.microsoft.com/sharepoint/v3"
   StaticName="Title">
  </Field>
 
  <Field ID="{67051A64-C702-4FD6-A7BC-9EECFCCCC033}" Type="Text" Name="MyTextField" DisplayName="My Text Field" />
  <Field ID="{21E0A786-8F94-4A7D-98C3-A8B06B8C592A}" Type="User" Name="MyUserField" DisplayName="My User Field" ShowField="NameWithPicture" />

  <!-- USE THE ID OF THE "NOTE" FIELD IN YOUR ELEMENTS.XML -->
  <Field ID="{6AAEB05A-2B38-482E-BC3A-FE2705E99709}"
  Type="Note"
  DisplayName="MyTaxonomyField_0"
  StaticName="MyTaxonomyFieldTaxHTField0"
  Name="MyTaxonomyFieldTaxHTField0"
  ShowInViewForms="FALSE"
  Required="FALSE"
  Hidden="TRUE"
  CanToggleHidden="TRUE"
  RowOrdinal="0" />
 
 <!-- USE THE ID OF THE "TAXONOMY" FIELD IN YOUR ELEMENTS.XML -->
  <Field ID="{B72AF39A-1F54-4041-93C2-F045CA8EDF04}"
   Type="TaxonomyFieldType"
   DisplayName="My Taxonomy Field"
   ShowField="Term1033"
   Required="TRUE"
   DisplaceOnUpgrade="TRUE"
   EnforceUniqueValues="FALSE"
   Group="_Custom"
   StaticName="MyTaxonomyField"
   Name="MyTaxonomyField">
 <Customization>
   <ArrayOfProperty>
  <Property>
    <Name>TextField</Name>
    <Value xmlns:q6="
http://www.w3.org/2001/XMLSchema"
     p4:type="q6:string"
     xmlns:p4="
http://www.w3.org/2001/XMLSchema-instance">{6AAEB05A-2B38-482E-BC3A-FE2705E99709}</Value>
  </Property>
   </ArrayOfProperty>
 </Customization>
  </Field>
</Fields>


You will see that the ID of the "Note" field is also added to the Customization metadata of the TaxonomyField. This tells it what note field to use.

5. Add the the field to the View of the List

Add your fields to the <ViewFields> section of every <View> like this:

<ViewFields>
  <FieldRef Name="MyTextField"></FieldRef>
  <FieldRef Name="MyUserField"></FieldRef>
  <FieldRef Name="MyTaxonomyField"></FieldRef>
</ViewFields>


6. Add the Event Receiver

Right click on your project and click Add > New Item...

Select the Event Receiver template from the SharePoint 2010 templates and click on Add. Select "List Events" as the type of receiver you want and choose the "A list was added" handler.
Add a reference to the Microsoft.SharePoint.Taxonomy.dll and add a using statement at the top of your class file.
Add the following method to your class that will connect the taxonomy field:

public void ConnectTaxonomyField(Guid fieldId, string listName, string termGroup, string termSetName, SPListEventProperties properties, string termName)
{
   if (properties.Web.Lists[listName].Fields.Contains(fieldId))
   {
    TaxonomySession session = new TaxonomySession(properties.Web.Site);

    if (session.DefaultKeywordsTermStore != null)
    {
     // get the default metadata service application               
     var termStore = session.DefaultKeywordsTermStore;
     var group = termStore.Groups.GetByName(termGroup);
     var termSet = group.TermSets.GetByName(termSetName);
     var term = termSet.GetByName(termName);

     TaxonomyField field = properties.Web.Lists[listName].Fields[fieldId] as TaxonomyField;
     // connect the field to the specified term               
     field.SspId = termSet.TermStore.Id;
     field.TermSetId = termSet.Id;
     field.TargetTemplate = string.Empty;
     field.AnchorId = term.Id;
     field.Update();
    }
    else
    {
     throw new TermStoreNotFoundException(string.Format("DefaultKeywordsTermStore not found in site {0}", properties.Web.Site.Url));
    }
   }
   else
   {
    throw new ArgumentException(string.Format("Field {0} not found in list {1}", fieldId, listName), "fieldId");
   }
}


Make a call to the ConnectTaxonomyField() method in your ListAdded event handler.

// Parameters are: Field ID, List Name, Term Group, Term Set, SPListEventProperties, Term you want to connect to
ConnectTaxonomyField(new Guid("{B72AF39A-1F54-4041-93C2-F045CA8EDF04}"), properties.ListTitle, "TermGroup", "TermSet", properties, "Term");


The last parameter is the Term you want to connect to in the Term Set. Not specifying this for a field will show you all the terms in that field.

Add the following classes to your file that will handle exceptions and create extension methods:

[Serializable]
public class TermStoreNotFoundException : Exception
{
 public TermStoreNotFoundException() { }
 public TermStoreNotFoundException(string message) : base(message) { }
 public TermStoreNotFoundException(string message, Exception inner)
  : base(message, inner) { }
 protected TermStoreNotFoundException(
  System.Runtime.Serialization.SerializationInfo info,
  System.Runtime.Serialization.StreamingContext context)
  : base(info, context) { }
}

public static class Extensions
{
 public static Group GetByName(this GroupCollection groupCollection, string name)
 {
  if (string.IsNullOrEmpty(name))
  {
   throw new ArgumentException("Not a valid group name", "name");
  }

  foreach (var group in groupCollection)
  {
   if (group.Name == name)
   {
    return group;
   }
  }
  throw new ArgumentOutOfRangeException("name", name, "Could not find the group");
 }

 public static TermSet GetByName(this TermSetCollection termSetCollection, string name)
 {
  if (string.IsNullOrEmpty(name))
  {
   throw new ArgumentException("Term set name cannot be empty", "name");
  }

  foreach (var termSet in termSetCollection)
  {
   if (termSet.Name == name)
   {
    return termSet;
   }
  }
  throw new ArgumentOutOfRangeException("name", name, "Could not find the term set");
 }

 public static Term GetByName(this TermSet termSet, string name)
 {
  if (string.IsNullOrEmpty(name))
  {
   throw new ArgumentException("Term name cannot be empty", "name");
  }

  for (int i = 0; i < termSet.Terms.Count; i++)
  {
   if (termSet.Terms[i].Name == name)
   {
    return termSet.Terms[i];
   }
  }
  throw new ArgumentOutOfRangeException("name", name, "Could not find the term set");
 }
}


7. Deploy the solution

You can simply deploy the solution by right clicking the project and clicking on Deploy.

That's it! You can now create an instance of the list to see your fields. It seems like a lot of work for just two fields, but once you get the basics right it goes quite fast.

Friday, September 9, 2011

How To: SharePoint 2010 Weather Web Part

Over the years working with SharePoint I have had many requests for a weather web part to be placed on the home page of a company’s intranet or web site. The problem has always been that the RSS feeds you find online are never that reliable, and even if you do find one the data that it returns is never structured in an easy-to-use way.

I have recently found out that Google has its own weather feed. The data is structured perfectly for what I want to do, and the following will show how I went about creating a custom weather web part. I will be adding properties for the user to select if they want the Current Conditions or Tomorrow’s Forecast and the City they live in. (For now these will only be South African cities.)

1. Create the Project in Visual Studio
Open up visual studio and select to create a new Visual Web Part project under the SharePoint > 2010 templates, enter a name and click OK. On the next window enter your site URL and click Finish.
For purposes of keeping my projects tidy I have renamed my Web Part, the user control and the class files to more appropriate names.




2. Set up the user control display

I have added the following to my WeatherWebPartUserControl.ascx file that will render the data:

<style type="text/css">
    .wwp-container { width: 200px; text-align: center; font-family:Arial; font-size:12px; }
    .wwp-city { font-size:14px; font-weight:bold; }
    .wwp-error { color:Red; }
</style>
<div class="wwp-container">
    <asp:Label runat="server" ID="lblCity" CssClass="wwp-city"></asp:Label><br />
    <asp:Label runat="server" ID="lblDate" CssClass="wwp-date"></asp:Label><br />
    <asp:Image runat="server" ID="lblImage" CssClass="wwp-image"></asp:Image><br />
    <asp:Label runat="server" ID="lblTemp" CssClass="wwp-temperature"></asp:Label>&nbsp;-&nbsp;
    <asp:Label runat="server" ID="lblCondition" CssClass="wwp-condition"></asp:Label>
    <asp:Label runat="server" ID="lblError" CssClass="wwp-error"></asp:Label>
</div>

3. Update the Web Part class

In your web part class file you are going to add properties for the web part that will display when you edit the web part in your page. These settings make it easy for users to control how the web part works:



Here's the code:

// enums for the values of the drop downs
public enum CityEnum { None, Johannesburg, Cape_Town, Durban, Pretoria, Bloemfontein };

public enum ForeCastDayEnum { Today, Tomorrow };
// public properties
public static CityEnum City;
public static ForeCastDayEnum ForeCastDay;

// these settings tell the web part how to display the controls
[Category("Settings"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("City"),
DefaultValue("None"), WebDescription("Select a city")]
public CityEnum _City
{
 get { return City; }
 set { City = value; }
}
// these settings tell the web part how to display the controls
[Category("Settings"),
Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("Forecast Day"),
DefaultValue("Today"), WebDescription("Select a city")]
public ForeCastDayEnum _ForeCastDay
{
 get { return ForeCastDay; }
 set { ForeCastDay = value; }
}

4. Update the User Control class

The class of the user control is where most of the processes happen. Here the methods are contained for accessing the RSS data and transforming it into displayable data. Below are the methods that I used. Comments are in line.

public const string _xmlUrl = "http://www.google.com/ig/api?weather={0},ZA&hl=en";
public const string _webUrl = "http://www.google.com/";
protected void Page_Load(object sender, EventArgs e)
{
 lblError.Text = string.Empty;
 try
 {
  WeatherWebPart.CityEnum selectedCity = WeatherWebPart.City;
  // check if a city has been selected
  if (selectedCity == WeatherWebPart.CityEnum.None)
  {
   throw new Exception("No city has been selected from the web part settings.");
  }
  else
  {
   GetWeatherData(selectedCity);
  }
 }
 catch (Exception ex)
 {
  lblError.Text = ex.Message;
 }
}

private void GetWeatherData(WeatherWebPart.CityEnum city)
{
 // format the url
 string url = String.Format(_xmlUrl, city.ToString().Replace("_", " "));
 // create the xmlreader
 XmlTextReader reader = new XmlTextReader(url);
 // load the reader into an xmldocument
 XmlDocument doc = new XmlDocument();
 doc.Load(reader);
 // get the root node
 XmlNode rootNode = doc.SelectSingleNode("/xml_api_reply/weather");
 // get the forecast data
 string cityName = rootNode.SelectSingleNode("forecast_information/city").Attributes["data"].Value;
 DateTime forecastDate = Convert.ToDateTime(rootNode.SelectSingleNode("forecast_information/forecast_date").Attributes["data"].Value);
 // check if the web part displays today or tomorrows weather
 if (WeatherWebPart.ForeCastDay == WeatherWebPart.ForeCastDayEnum.Today)
 {
  ShowToday(rootNode, cityName, forecastDate);
 }
 else
 {
  ShowTomorrow(rootNode, cityName, forecastDate);
 }
}

private void ShowTomorrow(XmlNode rootNode, string cityName, DateTime forecastDate)
{
 // get tomorrows conditions
 string tomorrowCondition = rootNode.SelectSingleNode("forecast_conditions[2]/condition").Attributes["data"].Value;
 string tomorrowHigh = rootNode.SelectSingleNode("forecast_conditions[2]/high").Attributes["data"].Value;
 string tomorrowLow = rootNode.SelectSingleNode("forecast_conditions[2]/low").Attributes["data"].Value;
 string tomorrowIcon = _webUrl + rootNode.SelectSingleNode("forecast_conditions[2]/icon").Attributes["data"].Value;
 string temperature = String.Format("{0}&deg;C - {1}&deg;C", tomorrowLow, tomorrowHigh);
 UpdateWebPartDisplay(cityName, tomorrowCondition, forecastDate.AddDays(1), temperature, tomorrowIcon);
}

private void ShowToday(XmlNode rootNode, string cityName, DateTime forecastDate)
{
 // get the current conditions
 string currentCondition = rootNode.SelectSingleNode("current_conditions/condition").Attributes["data"].Value;
 string currentTemperature = rootNode.SelectSingleNode("current_conditions/temp_c").Attributes["data"].Value + "&deg;C";
 string currentIcon = _webUrl + rootNode.SelectSingleNode("current_conditions/icon").Attributes["data"].Value;
 UpdateWebPartDisplay(cityName, currentCondition, forecastDate, currentTemperature, currentIcon);
}

private void UpdateWebPartDisplay(string city, string condition, DateTime date, string temperature, string iconUrl)
{
 lblCity.Text = city;
 lblCondition.Text = condition;
 lblDate.Text = date.ToLongDateString();
 lblTemp.Text = temperature;
 lblImage.ImageUrl = iconUrl;
}

5. Deploy the web part and install it on the page.

You can deploy the web part by simply right-clicking your project file and clicking Deploy. The solution will deploy to the URL that you specified in the setup stages of the project.

Here is the final look of the web part:


Thursday, September 1, 2011

10 ways to optimize your website for better Search Engine rankings – Part 3

Google Adwords

Adwords is a product from Google that helps to advertise your website. Basically how it works is, you get Google to advertise your website as one of their sponsored links based on keywords that are searched for and for every click that goes through on these links you will pay them a certain amount.
This means that even though searching for the phrase “peanut butter” only brings your site on the 27th page of the search results, it will still display on the first page of the results because of the ad campaign and this in turn generates more traffic for you website.
You might think that this can be quite costly if all of a sudden there is a shortage on peanut butter in some country and you get 10000 visitors, but Google allows you to set a maximum limit on what you are willing to pay.
Regular Content Updates
Updating the content on your website is very important to keep the crawlers coming back to your site. The more you update the content, the more relevant it will be to search results.

 Updating content does not mean rewriting your entire About Us page every month, but at least update some sites images, galleries or articles about your company.
Blogs
Ok so I’m not sure if blogs can fall under Social Media as well, but in any case it plays a big role on its own. Think about it this way:
You create a blog on a popular blogging site like blogger, wordpress, drupal, squarespace etc. This blog can be read by anybody on the web. Every blog post has something very relevant to your business and your website and contains a link to your site, images or gallery items. Boom! You have a semi-not-so-famous-yet website.
Google Page Speed
Google Labs has recently launched a new tool called Page Speed Online that will help you detect issues in your website that might cause it load slowly.
All you need to do is browse to http://pagespeed.googlelabs.com/, enter your website address and click Analyse Performance. The page will return with a description of what High Priority, Medium Priority and Low Priority respectively means. On the left hand side you will see a list of things you can look at to help speed up your website.

Friday, August 19, 2011

10 ways to optimize your website for better Search Engine rankings – Part 2

Flash

Even though flash looks really... well... flashy... and it makes your site interactive, it does not really do anything to help along your ranking. Let say you create your whole top navigation for your website using flash: You will have 5 menu items, with 5 sub items each. That’s 25 links that could be used by search crawlers to index your site better. However, with flash, your menu will just be placed as an <object> on in your markup and this is how the crawlers will see it.


You can however use flash to have some rotating images or similar on your site, but make sure if you have multiple links with it that they are placed separately and not in the object itself.
Images
It is very important to add meta data for ALL your images on your site. If you have a bunch of images on your site with no meta data, the crawlers are going to find them but won’t be able to link them back to anything. Remember that the crawlers only find the image object in your markup and can’t read anything out of the actual image. (Google has however added on an image search feature where you can specify colours which is pretty neat.)

When creating images, add alt and title properties that hold all the information about the image. In this way the search engines can link these when you do image searches.

<img src=”images/one.jpg” alt=”a big one in a star” title=”a big one in a star” />

Social Media

A great way to make your website more popular is to get it on as many social networking sites as possible. As I’ve said before, the more links you have to your site, the better – and having your link posted all over social networking sites does exactly that.

Of course it is also better to have your links on the most popular ones first, since the better your site ranks in search engines, the more the crawlers will run through it to check for new content. Thus if your link is on Facebook, Flickr, Tumblr, Twitter, etc. you know that external linking will happen quickly.



End Part 2

Thursday, August 4, 2011

10 ways to optimize your website for better Search Engine rankings – Part 1

Over the past few years I have had the opportunity to create a few basic web sites for friends and family. Now most of these are 5-6 pagers with a little bit of content you put in there to only serve as a place holder at the time, so no real thought has been put into how this affects your search ranking. After adding some analytics on my sites, I saw most of them are pretty far down in the results. The following are some basic tips on how to get our page out of the gutter and in to a respectable search rank.


Keywords
Search engine spiders regularly crawl through the web to rebuild their indexes. When this happens you want to be sure that it is getting the right information for the searches to be accurate. The crawlers check the wording on your site and based on those keywords it will place your site in a specific rank when that subjects is searched on.


The Google Webmaster Tools can help you track your keywords and will help you get more accurate results. Here is an example of keywords not set up quite right:

As you can see for this Jewellery site, the word gallery was found double the amount of times more than the word diamond which is more in line with the business. Clicking on a word will give you more details around where this keyword is found. Remember that keywords are not just related to your content; they can also come from page names and other Meta data in the HTML. The gallery items below were all pulled from links on images:



HTML Headings and Paragraphs


Content on websites is often put straight into <DIV>’s or <TD>’s which will cause Search Engines to not be able to tell the difference between content and headings on your page. Whenever you are converting content for your page into HTML make sure that your headings go into actual heading tags - These are <h1>, <h2>, <h3>, etc. The text for your column should preferably go into paragraph tags - <p>.


A search engine will recognise these and then indexing will be much more effective. I myself never wanted to use these because of the predefined styles for these tags, but I have noticed that you are free to customise them and they will still work as expected. Here is an example of how a content area should look:
<style>
                h1{
                                color:#00FF00; font-size:12px;
                }
                p{
                                color:#FF0000; font-size:10px;
                }
</style>
<div id="content-area">
                <h1>Heading 1</h1>
                <p>This is where your content goes</p>
</div>

Links
Links play a very important role in helping search engines discover your site properly. Search engine crawling will move through your content, detecting your keywords, images, etc. – but for every link it finds it will be able to move to that page and index it. If you have set up your site properly you shouldn’t have an issue with search engines not finding a page, but adding links throughout your site to other pages will just help the process on and could potentially increase your search rating.


A good way to help the engine crawl is to specify a site map in Google Webmaster Tools. This is a little xml file that contains the structure of your site. It looks something like this
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
                                <loc>http://yoursite.com/page.aspx</loc>
</url>
<url>
                                <loc>http://yoursite.com/subsite/page.aspx</loc>
</url>
</urlset>

In much the same way you can also specify pages that you don’t want search engines to crawl by adding a robots.txt file to your site. This file usually contains URLs to places that you don’t want the public to find e.g. login pages, checkout pages, administration pages, etc. This file is also manageable through your Google Webmaster Tools.
Another thing to keep in mind on links is the way they are placed inside your content. Content owners usually create links on pages by writing something like “We have a gallery for bikes and cars, click here to view it”. This type of linking should be avoided not only because the words “click" and "here” are going to be indexed, but also because you could in fact use that space for more keywords.
Rather create descriptive links that can possibly help increase your page ratings in search engines e.g. “we have some cars and bikes in our gallery”. In this way the Search engine could display a link to that page directly when someone searches for “cars and bikes”, whereas with the other one it won’t find anything.
End Part 1