Wednesday, February 23, 2011

List.DefaultView is null ?

I bet many of you fall for this one… I know I did.

You got a SPList object, you want to get the default view, or the default view name, so you write:

list.DefaultView.Title

You never even bother to check if DefaultView is null or not, right? Because it doesnt make sense…

Well, I had a customer who did something crazy.

He created a redirection page inside a list forms folder, and marked it as the default view for the list using SharePoint Designer 2007.

* You will be glad to hear that in SharePoint Designer 2010 this is not possible anymore :)

Well, from this point on, calling the list.DefaultView object returns null, which made our code fail since we didn’t think of checking if there is a valid default view for the list.

So, a lesson to be learned: never trust anyone in SharePoint!

A quick fix I did for it is to see if list.DefaultView is null, and if so, to loop through all list views and get the first valid view.

Here is the code I used:

internal static SPView GetSafeListDefaultView(SPList list, string hideViews)
{
if (list.DefaultView != null)
return list.DefaultView;
else
{
foreach (SPView view in list.Views)
{
if (
view.Hidden ||
string.IsNullOrEmpty(view.Title.Trim()) ||
//Explorer View or subject view in discussion - allow subject views.
(view.BaseViewID == "3" && view.ContentTypeId.ToString() == "0x") ||
(hideViews.IndexOf(";" + view.Title.ToLower() + ";") >= 0) ||
//reply views in discussion board
(list.BaseTemplate == SPListTemplateType.DiscussionBoard && !view.ContentTypeId.ToString().StartsWith("0x012001"))
)
continue;

return view;
}
}

throw new Exception("Could not get a safe default view for this list: " + list.Title);
}

So from now on,  I will use this method to get the list default view, and never again trust the list.DefaultView property, which may be null.


By the way, the proper way to do what the customer wanted is to change the list.RootFolder.WelcomePage to the redirection page he created. Changing the default view is not the best idea, and I am not sure if it is supported.


Cheers, Shai.

Tuesday, February 22, 2011

Assembly redirection in OWSTimer

You may have read my older post on how to add BindingRedirect for assemblies into web.config in SharePoint, which allows you to change the DLL version number of your SharePoint customizations.

One thing I did not mention was, what if you need to use one of these redirected DLL’s in an SPTimerJob?

You will notice that assembly BindingRedirect does not apply to OWSTimer.exe, and it will throw errors such as “Could not load file or assembly 'AssmeblyName, Version=1.2.60.0, Culture=neutral, PublicKeyToken=xxxxxxxxx' or one of its dependencies. The system cannot find the file specified”, while you alraedy have a newer version deployed.

So, how do I make OWSTimer.exe aware of the new version and have my SPTimerJobs running with the latest version every time?

First, we need to understand why this doesn’t work.

Timer jobs run from OWSTimer.exe file, which is not effected by the web.config settings, so our BindingRedirect in web.config file will not help our code to find the correct assembly when running of the OWSTimer.exe file.

Now, for the solution

The bad news are that although a WSP natively allows you to add BindingRedirect to your web.config, it does not allow the same for other config files.

So, we need to do this manually.

Not to fear though! There is a rather simple way of doing that, still within the WSP with no need to come out of the WSP deployment.

It is a little know fact, that a farm feature event handler, with the “FeatureInstalled” event will have 2 special effects:

1. It will run for each web front end server, and not only on one of them

2. It will allow sufficient access to add files to the SharePoint Root.

Just make sure you do not edit / remove any of the out of the box files!

Well, taking this into consideration makes our problem a very simple one to solve.

We just need to create a config file for OWSTimer.exe and add our BindingRedirect statements there, and that’s it – the OWSTimer will know which version of our DLL to look for.

Here is a code example on how to add or update 2 BindingRedirect nodes in that config file, the same method can apply to any other config file during WSP deployment stage:

public void UpdateOWSTimerBindingRedirect()
{
try
{
string configFile = SPUtility.GetGenericSetupPath("TEMPLATE").ToLower().Replace("\\template", "\\bin") + "\\OWSTIMER.EXE.CONFIG";
//string XMLData = System.IO.File.ReadAllText(configFile, Encoding.UTF8);
XmlDocument config = new XmlDocument();
config.Load(configFile);

//ensure assemblyBinding exists
XmlNode assemblyBinding = config.SelectSingleNode("configuration/runtime/*[local-name()='assemblyBinding' and namespace-uri()='urn:schemas-microsoft-com:asm.v1']");

if (assemblyBinding == null)
{
assemblyBinding = config.CreateNode(XmlNodeType.Element, "assemblyBinding", "urn:schemas-microsoft-com:asm.v1");
config.SelectSingleNode("configuration/runtime").AppendChild(assemblyBinding);
}

//Delete old entrees if exist
XmlElement current = assemblyBinding.FirstChild as XmlElement;
while (current != null)
{
XmlElement elmToRemove = null;
if (current.FirstChild != null)
{
var asmIdn = (current.FirstChild as XmlElement);
if (asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.sharepoint.foundation") ||
asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.foundation"))
elmToRemove = current;
}

current = current.NextSibling as XmlElement;

if (elmToRemove != null)
assemblyBinding.RemoveChild(elmToRemove);
}

XmlElement dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.SharePoint.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />"+
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}

dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />" +
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}

config.LoadXml(config.OuterXml.Replace("xmlns=\"\"",""));
config.Save(configFile);
}
catch { }
}

Simply call this method during the FeatureInstalled event and update the code with your DLL name and version number, and you are done.

Hope this helps you with file versioning on SharePoint, which can be a rather difficult task sometimes.


Thanks, Shai.

Tuesday, February 15, 2011

Adding print functionality to Data View Web Parts

Hi,
A question I got from a customer today was “How can I add a print button to data view web part which will print all documents in the view?”

Well, we have a product called iMUSH Print that allows you to print selected items/document from out of the box list views, and merge them into one PDF file for easy printing.

But the question remains, how do one connects this print functionality to a data view web part?

Well, lists or libraries, the answer is simple and took me about an hour to create a working POC.
First, lets have a look at out of the box iMUSH print feature in standard library view:
image
One thing to note, is that you can either use the checkboxes to select items for printing or click the print menu and select what you wish to print in the popup:
image
Ok, so obviously, this popup “knows” how to get a list of items for print. We will use this parameter to send the items form the DVWP.
Now, to the DVWP in SharePoint Designer:
2 changes are needed in the DVWP,

1. Add a print button HTML just before the <table> tag of the DVWP items:
<img alt="print" src="/_layouts/KWizCom_iMushFeature/ico_imush_print_16px.png"
onclick="KWizCom_DoDVWPPrint('/iMUSH/_layouts/KWizCom_iMushFeature/KWizComPrintList.aspx',
'567836FC-F9B1-4D48-80AC-D637772703D4', '', this.nextSibling);"/>
you will need to replace the url “/iMUSH/” with your real site URL, and the list id ‘5678…’ with your actual list/library GUID.

2. Locate the <tr> tag inside your DVWP items table, and add an “itemid” attribute to it:
<tr itemid="{@ID}">

That’s it for the DVWP.

Next you will have to add some javascript code to your page, anywhere in the page.
My favourite way about it is just adding a content editor web part.
Just insert this JS code:
<script>
function KWizCom_DoDVWPPrint(pageUrl, listId, viewId, dvwpTable)
{
var url = pageUrl + "?ListId=" + listId + "&View=" + viewId + "&ItemId=";
    //get items from dvwp
var items = "";
    for(var i = 0; i < dvwpTable.rows.length; i++)
  if(dvwpTable.rows[i].itemid != undefined)
        {
    if( items != "" ) items += ",";
            items += dvwpTable.rows[i].itemid;
  }
    url += items;
commonShowModalDialog(url,
'dialogHeight:300px;dialogWidth:180px;scroll:true;toolbar:no;status:no;resizable:yes;',
null, null);
}
</script>

and you are done!

Now, you can click print on the DVWP to print all items that are displayed in the web part, even after filtering but the user:


image


That’s it!

Just remember, this functionality requires a license for iMUSH Print.

Thursday, February 3, 2011

Map folder in SharePoint Root

I am sure many of you needed from time to time to get a local path to a file that was deployed to the SharePoint Root (AKA Hive in 2007 version, AKA “12” folder AKA “14” folder).

For whatever reason you needed to do so, you probably notices that the ASP.NET API (Server.MapPath) or any other ASP.NET method of discovering the local path for a file or folder did not work out as excepted.

Without going into a long explanation on why that did not work, There is a very little documented method that I have used a lot in the past and I have learned not many developer know it.

The SPUtility class contains a static method named “GetGenericSetupPath” that takes one string parameter.

That string parameter can be any folder under the SharePoint Root folder, and the result of the method is the local file system path of that folder.

For example, to get the local path for “Template” folder, use this command:
SPUtility.GetGenericSetupPath("Template");

But to get the “Layouts” folder, you have to use this command:
SPUtility.GetGenericSetupPath("Template\\LAYOUTS");

Now, saying that – you have to keep in mind that although you do have read permissions to these folders while inside a SharePoint APP, other permissions (like create file, or create folders) may be blocked.

Cheers,

Shai.

Tuesday, February 1, 2011

How to identify a Ribbon control ID

A frequent question I get during my “How to develop for the SharePoint Ribbon” session is:

How can I identify and existing ribbon control or group ID?

This is useful when you wish to override, hide a control, or insert new controls into an existing group / new groups into an existing tab.

So, this task is pretty simple once you know this trick:

First, open a page and make your ribbon control visible:

image

Second, use IE developer tool bar to select the ribbon button you want:

image

 

Climb up the HTML to the span container, the span ID will have the ribbon control ID:

image

In my case, for the wiki page “email a link” here is what we get:

Ribbon.WikiPageTab.Share-LargeLarge-0-0

So, the control ID will be:

“Ribbon.WikiPageTab.Share”

And there you go!