Friday, 23 December 2011
Problem restoring a PowerShell backup
I had a problem today attempting to
restore a backup I had taken using PowerShell. I received the following error in PowerShell when running thr
Restore-SPSite command:
"Restore-SPSite : Your backup is from a different version of Microsoft SharePoint Foundation and cannot be restored to a server running the current version. The backup file should be restored to a server with version '4.1.10.0' or later."
I checked my SharePoint version numbers against the server where I had taken the backup and it all looked fine. I took another backup in case the first one had become corrupt (even though I had already restored it successfully several times before). I rebooted and restarted my VM, but all to no avail.
In a last ditch attempt, I decided to delete 2 empty content databases that were set up on my machine, and created a new one to replace them and tried again.
To my surprise, it worked. So, if you have the same problem, try removing content databases (if you can).
"Restore-SPSite : Your backup is from a different version of Microsoft SharePoint Foundation and cannot be restored to a server running the current version. The backup file should be restored to a server with version '4.1.10.0' or later."
I checked my SharePoint version numbers against the server where I had taken the backup and it all looked fine. I took another backup in case the first one had become corrupt (even though I had already restored it successfully several times before). I rebooted and restarted my VM, but all to no avail.
In a last ditch attempt, I decided to delete 2 empty content databases that were set up on my machine, and created a new one to replace them and tried again.
To my surprise, it worked. So, if you have the same problem, try removing content databases (if you can).
Friday, 2 December 2011
Usability Testing
Browsing through some of the SPC2011
conference materials, I came upon a useful website that provides usability
testing from the perspective of how a user would interact with your web design.
You simply provide a web design image or HTML I believe and a series of tests are run to assess
how your design is used by real-world users.
The click responses to questions about the design are grouped and displayed so you can see whether what you thought was a prominent design for something important actually does the job of attracting user interest.
It's not a free site, but a test is only $9 (about £5.70 at today's price) which is possibly well worth the pay-out to get some useful design feedback upfront of any development work.
Check out http://www.intuitionhq.com.
The click responses to questions about the design are grouped and displayed so you can see whether what you thought was a prominent design for something important actually does the job of attracting user interest.
It's not a free site, but a test is only $9 (about £5.70 at today's price) which is possibly well worth the pay-out to get some useful design feedback upfront of any development work.
Check out http://www.intuitionhq.com.
Wednesday, 30 November 2011
Search Refiners and the Search Box
Something that isn't normally the
case is to remove the Search Box from the out-of-the-box search results page.
More often than not, it's a requirement to have it present so the user can see
the query they're currently searching with. However, recently, a client
insisted on removing this from their search results page.
Now, I've already given the game away here in the title of this article: some of the search refiners functionality stopped working. Which bit? Well, nothing overly important - simply the "show more" option that appears in the refiners when more than a specified number of refiners are present. More to the point is why it happens?
Having examined pages with and without the Search Box, it appears the Search Box Web Part is responsible for adding a JavaScript function and a reference to the search.js file. The function is effectively a check for the search.js, but without it the "show more" option will fail as it calls this function first and as it can't find it, it doesn't work.
The following was an extract I added to my Search Results Xslt to get the page to function correctly:
Now, I've already given the game away here in the title of this article: some of the search refiners functionality stopped working. Which bit? Well, nothing overly important - simply the "show more" option that appears in the refiners when more than a specified number of refiners are present. More to the point is why it happens?
Having examined pages with and without the Search Box, it appears the Search Box Web Part is responsible for adding a JavaScript function and a reference to the search.js file. The function is effectively a check for the search.js, but without it the "show more" option will fail as it calls this function first and as it can't find it, it doesn't work.
The following was an extract I added to my Search Results Xslt to get the page to function correctly:
function SearchEnsureSOD() {
EnsureScript('search.js', typeof(RenderTaggingControl));
}
_spBodyOnLoadFunctionNames.push('SearchEnsureSOD');
EnsureScript('search.js', typeof(RenderTaggingControl));
}
_spBodyOnLoadFunctionNames.push('SearchEnsureSOD');
The "typeof" used by
the EnsureScript function call is usually against GoSearch but this is present
only when the Search Box Web Part is present. I've used RenderTaggingControl
instead as this is present when the refiners are present.
Monday, 28 November 2011
CSS and JavaScript Bundling and Minification
Unfortunately, this is not something
immediately available, but it looks like this will be in the next version of
Visual Studio. The bundling features I've read about will automatically bundle
a specified selection of files into a single HTTP request.
The astute among you will probably already be aware that there's already support for JavaScript "bundling" in the current version of Visual Studio (and SharePoint) in the form of the Script Manager control which you can use to create what's called a Composite Script and something we've exploited in our SharePoint 2010 publishing projects. As the name implies, it only manages scripts so what it lacks is any kind of support for CSS.
The next version appears to not only enhance the current "bundling" functionality but also extend it for use with CSS files (and there may even be some possibility of extending this to other file types if the class model will allow). This means that multiple CSS files can then be "bundled" in the same way, thereby minimising the number of HTTP requests. Not only can we do that, but we will also be able to minify the bundled files.
Minification is the process of removing unnecessary characters from a file, like spaces, carriage-returns, possibly semi-colons, comments etc. It can be used to reduce files size considerably, and if the file size is small, the HTTP request will be quicker to return to the browser. There are already sites out there that can be used to do this, but to have it within Visual Studio will be very useful. How much this will be integrated with SharePoint I don't know. As SharePoint is based on a specific version of the .Net framework this functionality may not make it into the next version - we'll have to wait and see.
The main drive behind these changes appear to come from the necessity to provide a better web UX for mobile devices where low and slow bandwidth can inhibit use of a website. We'll just have to wait and see how and if it surfaces in the next version of SharePoint.
If you want to know more, check out Scott Guthrie's blog post: http://weblogs.asp.net/scottgu/archive/2011/11/27/new-bundling-and-minification-support-asp-net-4-5-series.aspx
The astute among you will probably already be aware that there's already support for JavaScript "bundling" in the current version of Visual Studio (and SharePoint) in the form of the Script Manager control which you can use to create what's called a Composite Script and something we've exploited in our SharePoint 2010 publishing projects. As the name implies, it only manages scripts so what it lacks is any kind of support for CSS.
The next version appears to not only enhance the current "bundling" functionality but also extend it for use with CSS files (and there may even be some possibility of extending this to other file types if the class model will allow). This means that multiple CSS files can then be "bundled" in the same way, thereby minimising the number of HTTP requests. Not only can we do that, but we will also be able to minify the bundled files.
Minification is the process of removing unnecessary characters from a file, like spaces, carriage-returns, possibly semi-colons, comments etc. It can be used to reduce files size considerably, and if the file size is small, the HTTP request will be quicker to return to the browser. There are already sites out there that can be used to do this, but to have it within Visual Studio will be very useful. How much this will be integrated with SharePoint I don't know. As SharePoint is based on a specific version of the .Net framework this functionality may not make it into the next version - we'll have to wait and see.
The main drive behind these changes appear to come from the necessity to provide a better web UX for mobile devices where low and slow bandwidth can inhibit use of a website. We'll just have to wait and see how and if it surfaces in the next version of SharePoint.
If you want to know more, check out Scott Guthrie's blog post: http://weblogs.asp.net/scottgu/archive/2011/11/27/new-bundling-and-minification-support-asp-net-4-5-series.aspx
Monday, 21 November 2011
Output a String With Escape Characters
Not something that is often required, but something that popped up today - how to get the literal output from a String object in C# including the escape characters.
So let's take a String variable declared like this:
string str = @"c:\temp";
The literal string is c:\temp, but what if we wanted the underlying escaped string which would look something like this:
"c:\\temp"
Not so easy. Fortunately, Google being the fount of all knowledge these days, I put it to work in finding some help and unearthed a neat example from Stack Overflow using an Extension Method:
internal static class ExtensionFunctions
{
internal static String ToLiteral(this String input)
{
var writer = new StringWriter();
CSharpCodeProvider provider = new CSharpCodeProvider();
provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, null);
return writer.ToString();
}
}
To use it simply type str.ToLiteral(); and it will return the string complete with the escape sequence intact.
So let's take a String variable declared like this:
string str = @"c:\temp";
The literal string is c:\temp, but what if we wanted the underlying escaped string which would look something like this:
"c:\\temp"
Not so easy. Fortunately, Google being the fount of all knowledge these days, I put it to work in finding some help and unearthed a neat example from Stack Overflow using an Extension Method:
internal static class ExtensionFunctions
{
internal static String ToLiteral(this String input)
{
var writer = new StringWriter();
CSharpCodeProvider provider = new CSharpCodeProvider();
provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, null);
return writer.ToString();
}
}
To use it simply type str.ToLiteral(); and it will return the string complete with the escape sequence intact.
Friday, 18 November 2011
Using Parameter Binding
Nothing new here, but Parameter
Bindings (see
Microsoft's article on them here) have been around for a while and more
widely publicised for use with the Data Form and Data View Web Parts. Something
not so widely know is that any web part inheriting from the Data Form Web Part
also has the same functionality available.
This means useful web parts like Search Results, People Search Results, Content by Query Web Parts, Xslt List View Web Part, RSS Aggregator web part and more all have this available to them.
In SharePoint 2010, apparent new functionality made things like the query string available to the underlying Xslt in a Content by Query Web Part. The truth is that this was available in 2007 by using Parameter Binding and the QueryString Location property (as I blogged previously).
What is more useful, is that the Search Results and People Search Results now include a Parameter Binding property available through the web part toolpane which means that level of configuration can be done through the UI rather than hacking away at the underlying web part file. Entering the Parameter Binding is easy enough using the syntax:
<ParameterBinding Name="[name]" Location="[selected location]" />
for each property you want to use. The [name] is added into the Xslt for the Web Part as an Xsl Param and it's then available.
Useful, simply and quick. Something to bear in mind.
This means useful web parts like Search Results, People Search Results, Content by Query Web Parts, Xslt List View Web Part, RSS Aggregator web part and more all have this available to them.
In SharePoint 2010, apparent new functionality made things like the query string available to the underlying Xslt in a Content by Query Web Part. The truth is that this was available in 2007 by using Parameter Binding and the QueryString Location property (as I blogged previously).
What is more useful, is that the Search Results and People Search Results now include a Parameter Binding property available through the web part toolpane which means that level of configuration can be done through the UI rather than hacking away at the underlying web part file. Entering the Parameter Binding is easy enough using the syntax:
<ParameterBinding Name="[name]" Location="[selected location]" />
for each property you want to use. The [name] is added into the Xslt for the Web Part as an Xsl Param and it's then available.
Useful, simply and quick. Something to bear in mind.
Thursday, 3 November 2011
Sandbox Solutions and the Update Panel
There are already several articles on
the web relating to the use of the ASP.NET UpdatePanel in sandbox solutions, so
to add to the mix, here's another one.
Sandbox solutions and the UpdatePanel: the truth of it is that you cannot use the UpdatePanel in sandbox solutions. If you try to use it you get an error stating "The control with ID 'UpdatePanel1' requests a ScriptManager on the page". Using the ScriptManager.GetCurrent returns null. If you try to add your own you get an error as you can only have one ScriptManager instance at a time. You can add your own, however, by adding it as the first control in the Form.Controls control tree, but even this will most likely break the page unless you duplicate the exact scripts you may be adding to the page in code - the symptom of this is the ScriptResource.axd that would normally contains your scripts returns with a 404.
That aside, the reason the ScriptManager.GetCurrent doesn't return the actual ScriptManager you have added to your page or master page is to do with the way sandbox solutions replace some objects with its own object types. For instance, the System.Web.UI.Page object is replaced by the SPUserCodePage object.
The question for me is why is the ScriptManager not available? Why not allow the use of the UpdatePanel in a sandbox?
Sandbox solutions and the UpdatePanel: the truth of it is that you cannot use the UpdatePanel in sandbox solutions. If you try to use it you get an error stating "The control with ID 'UpdatePanel1' requests a ScriptManager on the page". Using the ScriptManager.GetCurrent returns null. If you try to add your own you get an error as you can only have one ScriptManager instance at a time. You can add your own, however, by adding it as the first control in the Form.Controls control tree, but even this will most likely break the page unless you duplicate the exact scripts you may be adding to the page in code - the symptom of this is the ScriptResource.axd that would normally contains your scripts returns with a 404.
That aside, the reason the ScriptManager.GetCurrent doesn't return the actual ScriptManager you have added to your page or master page is to do with the way sandbox solutions replace some objects with its own object types. For instance, the System.Web.UI.Page object is replaced by the SPUserCodePage object.
The question for me is why is the ScriptManager not available? Why not allow the use of the UpdatePanel in a sandbox?
Wednesday, 26 October 2011
Problems Adding Rest Service Reference in Visual Studio
If you ever have to add a Service
Reference for the SharePoint List WCF Service to a project in Visual Studio
2010 you may encounter a "Bad Request" or "There was an error downloading ..."
type message even though you can view the results of the same service through a
browser.
The way around this is very simple and resolves the problem, however, in my mind, this should only really be done on development machines to get around this issue and not rolled out across a Live installation. What you need to do is go to IIS and change the authentication methods for the _VTI_BIN folder to disable Anonymous access. That's it. You can then successfully add the service reference.
The way around this is very simple and resolves the problem, however, in my mind, this should only really be done on development machines to get around this issue and not rolled out across a Live installation. What you need to do is go to IIS and change the authentication methods for the _VTI_BIN folder to disable Anonymous access. That's it. You can then successfully add the service reference.
Tuesday, 18 October 2011
Custom Workflow Actions in SharePoint 2010
On a recent SharePoint 2010 project I
had the opportunity to look at creating custom Workflow Actions which is a way
to extend the actions already available in SharePoint Designer Workflows.
If I'm honest, the foray was both informative and slightly arduous but perhaps due mostly to my own inability to type properly and my own lack of understanding. Let's start by having a look at why we decided to look down this path.
The project in question required a handful of workflows to be developed, all
of which were prepared and written using SharePoint Designer. The problem was
highlighted early on that we needed to port the be able to package the workflows
up to deliver them across multiple platforms during the development life-cycle.
For 2 of the workflows, this didn't pose a problem since they were Reusable Workflows which lend themselves to just this kind of functionality. The 2 workflow, however, was a List-based workflow and as such it was not able to be exported as a WSP. The reason the workflow was a List-based workflow was due to the columns necessary to facilitate the workflow actions - Approval Status being the culprit in this scenario. As it turns out, the "Approval Status" column is not visible for anything other than a List-based workflow.
To allow the workflow to be packaged up meant it had to be a Reusable Workflow, or we had to provide a work-around. After several attempts to package the workflow up (using this method http://ikarstein.wordpress.com/2011/02/10/walkthrough-how-to-move-or-copy-a-sharepoint-designer-2010-list-workflow-to-another-list-on-the-same-site-or-another-site/ was a way to export a List-based workflow using the "Export to Visio" and "Import from Visio" options which did work but didn't provide the hands-off approach we wanted) we decided to try writing a custom Workflow Action to perform the steps we required.
As it turns out, creating a custom Workflow Action is pretty
straight-forward. Create a class, create a publicly declared static method in
the class, and away you go. The first parameter must be a context object - we
were deploying as a User Code Solution so this was of type
SPUserCodeWorkflowContext.
If you're action doesn't need any parameters, do your coding stuff and then move on to the feature XML.
One note here - any typos or spelling mistakes mean that your workflow will not run. I battled for a long time to debug my workflow with only frustration as my reward. I eventually searched the SharePoint logs and found that I had mis-named a parameter so my workflow was never being run. Once I had fixed that, I was away after I had attached to the SharePoint User Code process.
Here's a couple of walk-throughs I found: http://msdn.microsoft.com/en-us/library/gg615449.aspx and http://msdn.microsoft.com/en-us/library/ff798499.aspx. What I would say is that the first article is more accurate. The second one makes no mention of the fact that the method you implement for your action should be a static method - even the example code doesn't show this, so beware.
End-to-end, the final solution (which wasn't overly complicated) took about a day, and at the end I had a fully working custom workflow action which I could use in my workflows. Looking back, it was actually quite painless and easy and once you know where to look (i.e. SharePoint logs, debugging etc) it was fairly rapid.
Unfortunately, there is no happy ending to this story. I went through a day
of trauma and frustration and came out the other side having happily implemented
my custom workflow action into a reusable workflow. I saved the workflow
through SharePoint Designer to a WSP and thought things were making sense.
I then moved the workflow to another site collection and installed the WSP and that's where the story currently ends. It didn't work. Well.....I say that it didn't work, but I don't really know if I'm honest. The only definition I have to say it didn't work is the fact that the workflow I had created did not appear anywhere so I was unable to use it. No doubt I will post further about this once I've figured out what went wrong here.
Looking back at the reasons behind our decision to use a workflow it was
mostly dictated by the approach to the required functionality we had already
started: namely the other 2 reusable workflows. Keeping to the same model for
the third workflow made sense as it followed the same technology instead of
departing in a different direction and introducing custom code instead of
manully created workflow (i.e. event receivers).
As it turned out, writing a custom workflow action still meant code in order to achieve the portability we wanted so at that point, perhaps, it would have been better to write an event receiver. The influence to stick with workflow came from the technology angle - keeping to the same "hymn sheet" as it were. Yes, it was custom code, but at least it was still workflow.
Fortunately, in this scenario we did have a fallback position to backup the site and restore it, so the time invested was as much for research into this area, which, if I'm honest, was time well spent.
If I'm honest, the foray was both informative and slightly arduous but perhaps due mostly to my own inability to type properly and my own lack of understanding. Let's start by having a look at why we decided to look down this path.
Why a Custom Workflow
Action?
For 2 of the workflows, this didn't pose a problem since they were Reusable Workflows which lend themselves to just this kind of functionality. The 2 workflow, however, was a List-based workflow and as such it was not able to be exported as a WSP. The reason the workflow was a List-based workflow was due to the columns necessary to facilitate the workflow actions - Approval Status being the culprit in this scenario. As it turns out, the "Approval Status" column is not visible for anything other than a List-based workflow.
To allow the workflow to be packaged up meant it had to be a Reusable Workflow, or we had to provide a work-around. After several attempts to package the workflow up (using this method http://ikarstein.wordpress.com/2011/02/10/walkthrough-how-to-move-or-copy-a-sharepoint-designer-2010-list-workflow-to-another-list-on-the-same-site-or-another-site/ was a way to export a List-based workflow using the "Export to Visio" and "Import from Visio" options which did work but didn't provide the hands-off approach we wanted) we decided to try writing a custom Workflow Action to perform the steps we required.
Nuts and Bolts
If you're action doesn't need any parameters, do your coding stuff and then move on to the feature XML.
One note here - any typos or spelling mistakes mean that your workflow will not run. I battled for a long time to debug my workflow with only frustration as my reward. I eventually searched the SharePoint logs and found that I had mis-named a parameter so my workflow was never being run. Once I had fixed that, I was away after I had attached to the SharePoint User Code process.
Here's a couple of walk-throughs I found: http://msdn.microsoft.com/en-us/library/gg615449.aspx and http://msdn.microsoft.com/en-us/library/ff798499.aspx. What I would say is that the first article is more accurate. The second one makes no mention of the fact that the method you implement for your action should be a static method - even the example code doesn't show this, so beware.
End-to-end, the final solution (which wasn't overly complicated) took about a day, and at the end I had a fully working custom workflow action which I could use in my workflows. Looking back, it was actually quite painless and easy and once you know where to look (i.e. SharePoint logs, debugging etc) it was fairly rapid.
The Fairytale Ending(?)
I then moved the workflow to another site collection and installed the WSP and that's where the story currently ends. It didn't work. Well.....I say that it didn't work, but I don't really know if I'm honest. The only definition I have to say it didn't work is the fact that the workflow I had created did not appear anywhere so I was unable to use it. No doubt I will post further about this once I've figured out what went wrong here.
Final Thoughts
As it turned out, writing a custom workflow action still meant code in order to achieve the portability we wanted so at that point, perhaps, it would have been better to write an event receiver. The influence to stick with workflow came from the technology angle - keeping to the same "hymn sheet" as it were. Yes, it was custom code, but at least it was still workflow.
Fortunately, in this scenario we did have a fallback position to backup the site and restore it, so the time invested was as much for research into this area, which, if I'm honest, was time well spent.
Monday, 12 September 2011
Text Links vs Image Links (SEO)
A slight departure from the normal SharePoint articles I normally write, so from a completely HTML design point of view, here's an interesting question that I've asked myself several times and only answered through my own experience and through quite a few assumptions.
If you have an image displaying text, how does that compare in terms of SEO (and by SEO I'm not referring to SharePoint search ranking here) against using the actual text itself in a link. To give an example, you may display a link using HTML markup like this:
<a href="/mylink/"><img src="thisisalink.jpg" alt="this is a link"/></a>
or like this:
If you have an image displaying text, how does that compare in terms of SEO (and by SEO I'm not referring to SharePoint search ranking here) against using the actual text itself in a link. To give an example, you may display a link using HTML markup like this:
<a href="/mylink/"><img src="thisisalink.jpg" alt="this is a link"/></a>
or like this:
<a href="/mylink/">this is a link</a>
Whichever way you decide, which one ranks better?
It seems there is no definitive answer, but there are quite a debates about the whole subject. I read an article recently that helps to clarify: http://www.kingrosales.com/2009/04/seo-debate-text-links-vs-image-based-links/ but even this makes some assumptions. Having said that, in my experience, I have always used text and I think on the logic provided in the article, I feel as though that's probably the right way to go.
Friday, 2 September 2011
Finding Feature IDs
If you're anything like me, sometimes you need to find out what the ID of a SharePoint feature is and it can be something of a trial to locate the exact ID, especially if there's no handy SharePoint Server to remote onto and then go and search the FEATURES directory. So, a much easier way to get the ID of a feature is to use the Developer Toolbar (if you're using IE). Turn on the developer toolbar and select the Activate button of the feature you're interested in using the element selector. The button is enclosed in a DIV which has an ID attribute as follows:
As you can see from the image, the ID attribute of the DIV is a GUID, and this GUID is the Feature ID. Simple.
As you can see from the image, the ID attribute of the DIV is a GUID, and this GUID is the Feature ID. Simple.
Monday, 22 August 2011
Getting today's date and current user in CQWP
If you've ever used a Data Form Web Part you'll know that you can set up some Parameter Bindings to pull in various SharePoint-specific values like querystring parameters and server/environment variables etc. It's only a small leap of faith to understand that since the Content by Query Web Part derives from the same web part these parameter bindings may also be available to the Content by Query Web Part and therefore able to be exposed in the XSL using the xsl:param command.
I've used the current Page Url and querystring in 2010 and have since used it in 2007 using the above tweaking for CQWPs hard-coded into a page layout. Two more useful values I came across were the User ID, which is the currently logged on user returned as a the User Name, and today's date returned using the ISO format we're familiar with in SharePoint. To use these, simply add a Parameter Binding as follows to a Parameter Bindings section in the CQWP parameters and then add an xsl:param into the XSL and hey-presto.
Add this section to your the CQWP HTML in your page:
<ParameterBindings>
<ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
<ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/>
</ParameterBindings>
and in the XSL add the following:
<xsl:param name="UserID />
<xsl:param name="Today" />
then use them as $Today and $UserID in the XSL.
I've used the current Page Url and querystring in 2010 and have since used it in 2007 using the above tweaking for CQWPs hard-coded into a page layout. Two more useful values I came across were the User ID, which is the currently logged on user returned as a the User Name, and today's date returned using the ISO format we're familiar with in SharePoint. To use these, simply add a Parameter Binding as follows to a Parameter Bindings section in the CQWP parameters and then add an xsl:param into the XSL and hey-presto.
Add this section to your the CQWP HTML in your page:
<ParameterBindings>
<ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
<ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/>
</ParameterBindings>
and in the XSL add the following:
<xsl:param name="UserID />
<xsl:param name="Today" />
then use them as $Today and $UserID in the XSL.
Labels:
Content Query Web Part,
SharePoint 2007,
SharePoint 2010,
XSL
Monday, 23 May 2011
Sending Emails using User/Sandboxed Solutions
If you've done any kind of work emailing using SharePoint you are probably aware of the SendEmail method available through the SharePoint object model as part of the Microsoft.SharePoint.Utilities namespace. It's very useful as it allows us to send emails with a minimum amount of effort and without having to know the SMTP settings and such like.
In user/sandboxed solutions, this method is not available so we're left in a bit of a limbo, not knowing how to achieve it as there's nothing else available in the object model that achieves the same thing. Let's say we have a custom "Contact Us" webpart which sends someone an email with the contact information: how can you achieve the same thing?
We could use the System.Net.Mail methods available to us, but is it reasonable to expect a content author to supply the webpart with the appropriate SMTP settings? Why is that an issue? Well, these settings can be accessed programmatically using the object model which would resolve this minor difficulty, but they are not available in a sandbox. They exist at Web Application level and are therefore off limits.
Fortunately, there is another way, and that is through using SharePoint Designer workflow. You can trigger a workflow to run programmatically and also supply information to the workflow and set the initiation values. Here's the code to start a workflow:
/// <summary>
/// Method to handle starting the appropriate Workflow as specified by the Site Collection Workflow Name property
/// </summary>
private void StartWorkflow()
{
SPWorkflowAssociation wfa = SPContext.Current.Web.WorkflowAssociations.GetAssociationByName(this.SiteCollectionWorkflowName, CultureInfo.InvariantCulture);
wfa.AssociationData = this.GetWorkflowInitiationData();
SPContext.Current.Site.WorkflowManager.StartWorkflow(null, wfa, this.GetWorkflowInitiationData(), SPWorkflowRunOptions.Asynchronous);
}
The StartWorkflow method is where the workflow starts and the GetWorkflowInitiationData is where the XML erquired to set the initiation properties are set up. From what I've seen, the XML stays pretty-much the same with properties being set using the "dataFields" namespace "d:". The property names are workflow initiation properties I set up in my workflow. The SiteCollectionWorkflowName used in the StartWorkflow method is a property of the webpart which could be hidden away.
In user/sandboxed solutions, this method is not available so we're left in a bit of a limbo, not knowing how to achieve it as there's nothing else available in the object model that achieves the same thing. Let's say we have a custom "Contact Us" webpart which sends someone an email with the contact information: how can you achieve the same thing?
We could use the System.Net.Mail methods available to us, but is it reasonable to expect a content author to supply the webpart with the appropriate SMTP settings? Why is that an issue? Well, these settings can be accessed programmatically using the object model which would resolve this minor difficulty, but they are not available in a sandbox. They exist at Web Application level and are therefore off limits.
Fortunately, there is another way, and that is through using SharePoint Designer workflow. You can trigger a workflow to run programmatically and also supply information to the workflow and set the initiation values. Here's the code to start a workflow:
/// <summary>
/// Method to handle starting the appropriate Workflow as specified by the Site Collection Workflow Name property
/// </summary>
private void StartWorkflow()
{
SPWorkflowAssociation wfa = SPContext.Current.Web.WorkflowAssociations.GetAssociationByName(this.SiteCollectionWorkflowName, CultureInfo.InvariantCulture);
wfa.AssociationData = this.GetWorkflowInitiationData();
SPContext.Current.Site.WorkflowManager.StartWorkflow(null, wfa, this.GetWorkflowInitiationData(), SPWorkflowRunOptions.Asynchronous);
}
/// <summary>
/// Method to construct the Workflow Initiation/Association Data
/// </summary>
/// <returns>A string containing the XML schema and values necessary for the Association Data</returns>
private String GetWorkflowInitiationData()
{
String schema = "<dfs:myFields xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
"xmlns:dms=\"http://schemas.microsoft.com/office/2009/documentManagement/types\" " +
"xmlns:dfs=\"http://schemas.microsoft.com/office/infopath/2003/dataFormSolution\" " +
"xmlns:q=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/queryFields\" " +
"xmlns:d=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields\" " +
"xmlns:ma=\"http://schemas.microsoft.com/office/2009/metadata/properties/metaAttributes\" " +
"xmlns:pc=\"http://schemas.microsoft.com/office/infopath/2007/PartnerControls\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<dfs:queryFields /><dfs:dataFields><d:SharePointListItem_RW>{0}</d:SharePointListItem_RW>" +
"</dfs:dataFields></dfs:myFields>";
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<d:EmailTo>{0}</d:EmailTo>", this.SendToEmailAddress);
sb.AppendFormat("<d:EmailSubject>{0}</d:EmailSubject>", this._subject.Text);
sb.AppendFormat("<d:EmailText>{0}</d:EmailText>", this._comments.Text);
sb.AppendFormat("<d:AutoResponseTo>{0}</d:AutoResponseTo>", this._email.Text);
sb.AppendFormat("<d:AutoResponseSubject>{0}</d:AutoResponseSubject>", this.AutoResponseSubject);
sb.AppendFormat("<d:AutoResponseText>{0}</d:AutoResponseText>", this.AutoResponseText);
return String.Format(schema, sb.ToString());
}
/// Method to construct the Workflow Initiation/Association Data
/// </summary>
/// <returns>A string containing the XML schema and values necessary for the Association Data</returns>
private String GetWorkflowInitiationData()
{
String schema = "<dfs:myFields xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
"xmlns:dms=\"http://schemas.microsoft.com/office/2009/documentManagement/types\" " +
"xmlns:dfs=\"http://schemas.microsoft.com/office/infopath/2003/dataFormSolution\" " +
"xmlns:q=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/queryFields\" " +
"xmlns:d=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields\" " +
"xmlns:ma=\"http://schemas.microsoft.com/office/2009/metadata/properties/metaAttributes\" " +
"xmlns:pc=\"http://schemas.microsoft.com/office/infopath/2007/PartnerControls\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<dfs:queryFields /><dfs:dataFields><d:SharePointListItem_RW>{0}</d:SharePointListItem_RW>" +
"</dfs:dataFields></dfs:myFields>";
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<d:EmailTo>{0}</d:EmailTo>", this.SendToEmailAddress);
sb.AppendFormat("<d:EmailSubject>{0}</d:EmailSubject>", this._subject.Text);
sb.AppendFormat("<d:EmailText>{0}</d:EmailText>", this._comments.Text);
sb.AppendFormat("<d:AutoResponseTo>{0}</d:AutoResponseTo>", this._email.Text);
sb.AppendFormat("<d:AutoResponseSubject>{0}</d:AutoResponseSubject>", this.AutoResponseSubject);
sb.AppendFormat("<d:AutoResponseText>{0}</d:AutoResponseText>", this.AutoResponseText);
return String.Format(schema, sb.ToString());
}
The StartWorkflow method is where the workflow starts and the GetWorkflowInitiationData is where the XML erquired to set the initiation properties are set up. From what I've seen, the XML stays pretty-much the same with properties being set using the "dataFields" namespace "d:". The property names are workflow initiation properties I set up in my workflow. The SiteCollectionWorkflowName used in the StartWorkflow method is a property of the webpart which could be hidden away.
Monday, 16 May 2011
Google Maps vs Bing Maps
One of the nice things about Google Maps is the ability to embed a static image of your map, rather than having to use a JavaScript API or resorting to downloading multiple JavaScript files to support the map's features. Simply using a URL to point to one of Google's services was enough when it was used as the SRC attribute of an IMG tag - simple. It was something that was missing, until fairly recently, from Bing Maps.
Fortunately, Bing Maps have caught up and now have a REST API available to support static imagery in the same way as Google Maps. The details can be found here: http://msdn.microsoft.com/en-us/library/ff701724.aspx.
Simply adding the correct URL to the SRC attribute of an IMG tag creates the correct image to embed thereby avoiding multiple JavaScript downloads and any use of a client-side API.
Fortunately, Bing Maps have caught up and now have a REST API available to support static imagery in the same way as Google Maps. The details can be found here: http://msdn.microsoft.com/en-us/library/ff701724.aspx.
Simply adding the correct URL to the SRC attribute of an IMG tag creates the correct image to embed thereby avoiding multiple JavaScript downloads and any use of a client-side API.
Publishing Images and Search Indexing
This may be something that you already know, but in SharePoint 2007 fields based on the Publishing Image field type caused problems when you wanted to use these kinds of field in the Search Results.
The problem became apparent if you wanted to use something like a Roll Up Image on content pages where you wanted to display the image both using Content Query Web Parts (which don't have a problem with Publishing Image fields) and in Search Results. The problem is, Search doesn't index fields of this type. It just doesn't. So you can set up your metadata property which you can add a property mapping for the Publishing Rollup Image field but when you try to surface the data contained by the property in search it contains nothing.
Having experienced this problem in 2007 during a custom Search project, I was already aware of the limitation and then SharePoint 2010 came around and a similar requirement came about. I was approached to take a look into whether it was still a problem and had some expectation that perhaps it had been resolved. Alas, it has not.
So, buyer beware!!
Fortunately, there is a simple work-around. Create a text field into which you automatically add the contents from your Publishing Image field as text by whatever means you prefer and then use that to set up your metadata property instead. Works like a charm.
The problem became apparent if you wanted to use something like a Roll Up Image on content pages where you wanted to display the image both using Content Query Web Parts (which don't have a problem with Publishing Image fields) and in Search Results. The problem is, Search doesn't index fields of this type. It just doesn't. So you can set up your metadata property which you can add a property mapping for the Publishing Rollup Image field but when you try to surface the data contained by the property in search it contains nothing.
Having experienced this problem in 2007 during a custom Search project, I was already aware of the limitation and then SharePoint 2010 came around and a similar requirement came about. I was approached to take a look into whether it was still a problem and had some expectation that perhaps it had been resolved. Alas, it has not.
So, buyer beware!!
How To Resolve
Fortunately, there is a simple work-around. Create a text field into which you automatically add the contents from your Publishing Image field as text by whatever means you prefer and then use that to set up your metadata property instead. Works like a charm.
Labels:
SharePoint 2007,
SharePoint 2010,
SharePoint Search
Wednesday, 13 April 2011
Removing Unnecessary CSS
I've been recently looking at performance of public-facing websites built with SharePoint 2010 and, following on from Chris O'Brien's blog on removing unnecessary JavaScript files for anonymous users that are loaded as part of the Script-on-Demand framework, I was browsing through the kinds of files requested and whether they were important or not.
One thing I noticed was that there are several CSS files also loaded by SharePoint that are not always necessary for anonymous users. Things like controls.css or forms.css and maybe even corev4.css in some cases may not be needed. Therefore, I decided to approach the problem with the same mindset already adopted for the additional JavaScripts which made some sense to be able to have a control which would be able to strip out unnecessary or superfluous JavaScript and CSS files. In addition to Chris O'Brien's code example, here's mine:
In the example above I haven't included Chris's code or class-level variables to make it slightly easier to read. Suffice to say that I've added his code into a separate method which is called from the OnPreRender event (see the commented out this.RemoveScripts(); line). I've also changed the OnInit slightly to check for empty strings prior to attempting to extract the entered value into the internal List variable.
One thing I noticed was that there are several CSS files also loaded by SharePoint that are not always necessary for anonymous users. Things like controls.css or forms.css and maybe even corev4.css in some cases may not be needed. Therefore, I decided to approach the problem with the same mindset already adopted for the additional JavaScripts which made some sense to be able to have a control which would be able to strip out unnecessary or superfluous JavaScript and CSS files. In addition to Chris O'Brien's code example, here's mine:
private List _cssFiles = new List();
private List _cssFileIndicesToBeRemoved = new List();
private List _foundCssFiles = new List();
public string CSSFiles { get; set; }
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// store the css files property into the class private List variables
if (String.IsNullOrEmpty(this.CSSFiles) == false)
{
string[] css = this.CSSFiles.Split(new char[] { ';' });
this._cssFiles.AddRange(css);
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// remove files but only for anonymous users
if (HttpContext.Current.User.Identity.IsAuthenticated == false)
{
// this.RemoveScripts();
this.RemoveCSS();
}
}
private void RemoveCSS()
{
int index = 0;
for (int iteration = 0; iteration < 2; iteration++)
{
IList files = null;
FieldInfo prop = null;
this._cssFileIndicesToBeRemoved = new List();
if (iteration == 0)
prop = this.GetFieldInfo(SPContext.Current, "m_css");
else
prop = this.GetFieldInfo(SPContext.Current, "m_orderedCss");
if (prop != null)
{
files = (IList)prop.GetValue(SPContext.Current);
foreach (var file in files)
{
FieldInfo cssRegisterProp = this.GetFieldInfo(file, "CssReference");
if (cssRegisterProp != null)
{
string cssFileName = cssRegisterProp.GetValue(file).ToString();
// add the file to the list of indices for removal
if (string.IsNullOrEmpty(this._cssFiles.Find(delegate(string sFound)
{
return cssFileName.ToLower().Contains(sFound.ToLower());
})) == false)
{
this._cssFileIndicesToBeRemoved.Add(index);
}
else
{
#if DEBUG
this._foundCssFiles.Add(cssFileName);
#endif
}
}
index++;
}
// reset the index counter and remove the required files
index = 0;
foreach (int j in this._cssFileIndicesToBeRemoved)
{
files.RemoveAt(j - index);
index++;
}
prop.SetValue(SPContext.Current, files);
}
}
}
private FieldInfo GetFieldInfo(object Type, string FieldName)
{
Type t = Type.GetType();
FieldInfo prop = t.GetField(FieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
return prop;
}
To explain a little. The CSS files are "registered" using the CSSRegistration control. When a CSS file is registered it gets stored in a CSSReferences property of the current SPContext which is an internal property of type ICollection<CssRegistrationRecord>. This property is read-only which means that while we can look at the collection we cannot write any changes back through this property. Fortunately, by looking at the Get for this property, we can see that it uses two private variables - m_css and m_orderedCss - to work out the ICollection contents. By accessing these two variables directly and changing them using reflection we can trim out the unnecessary files in the same way that we can trim out unwanted JavaScript files for anonymous users.In the example above I haven't included Chris's code or class-level variables to make it slightly easier to read. Suffice to say that I've added his code into a separate method which is called from the OnPreRender event (see the commented out this.RemoveScripts(); line). I've also changed the OnInit slightly to check for empty strings prior to attempting to extract the entered value into the internal List variable.
Labels:
CSS,
JavaScript,
SharePoint 2010,
SharePoint 2010 Performance
Thursday, 7 April 2011
UI Design and Taxonomy
Sometimes, when you're designing a UI for a better experience for Users, you want to use some of the functionality usually reserved for content authors.
What do I mean? Well, normally in Publishing Sites, when you're in edit mode you expose components and UI to content authors and allow a great user experience. The page becomes published and then that same data might be shown to readers of the page as simple text or perhaps formatted in some HTML - either way, it's exposed as static data. But suppose you have a form which readers need to interact with to, say, select a set of search criteria which you have available.
One such part of SharePoint 2010 which is likely to be used frequently is Managed Metadata and taxonomy. It may be you want to allow Users to select a Term from your Term Set in search rather than allowing a manual text input field. Fortunately, this is something someone has thought about at Microsoft and therefore created the Taxonomy Web Tagging Control.
First of all, a little about why a separate control is necessary.
In Publishing, due to the way Page Layouts are closely tied in with Content Types Fields are exposed using the Field Controls which, obviously, use the underlying Field from the Content Type to store and retrieve it's data. Outside of a Page Layout the Field Controls in general fall down as they rely on this close relationship for their supply and storage and therefore cannot be used unless the underlying item has a Field within its Content Type that matches.
Taxonomy Field Controls are no different, so therefore the Taxonomy Web Tagging Control steps in to allow coupling to the Term Store and Term Set directly, bypassing the need for a Content Type and freeing up the control to be used just about anywhere (see the hyperlink below for more information about this component).
Two slightly awkward bits of information are required: the Term Store ID (exposed as the SSPList property) and the Term Set ID (exposed as the TermSetList property). How do you get these?
Open up Central Admin and go to your Managed Metadata service - you'll see the Term Store ID exposed in the URL. If you navigate the Term Store to the Term Set you want to use and open up something like the IE Dev Toolbar you can step through the HTML structure to the LI tag of the Term Set and the ID attribute of the LI tag is your Term Set ID.
Here's some more information from Microsoft on the Web Tagging Control and how to use it: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.taxonomy.taxonomywebtaggingcontrol.aspx
What do I mean? Well, normally in Publishing Sites, when you're in edit mode you expose components and UI to content authors and allow a great user experience. The page becomes published and then that same data might be shown to readers of the page as simple text or perhaps formatted in some HTML - either way, it's exposed as static data. But suppose you have a form which readers need to interact with to, say, select a set of search criteria which you have available.
One such part of SharePoint 2010 which is likely to be used frequently is Managed Metadata and taxonomy. It may be you want to allow Users to select a Term from your Term Set in search rather than allowing a manual text input field. Fortunately, this is something someone has thought about at Microsoft and therefore created the Taxonomy Web Tagging Control.
First of all, a little about why a separate control is necessary.
In Publishing, due to the way Page Layouts are closely tied in with Content Types Fields are exposed using the Field Controls which, obviously, use the underlying Field from the Content Type to store and retrieve it's data. Outside of a Page Layout the Field Controls in general fall down as they rely on this close relationship for their supply and storage and therefore cannot be used unless the underlying item has a Field within its Content Type that matches.
Taxonomy Field Controls are no different, so therefore the Taxonomy Web Tagging Control steps in to allow coupling to the Term Store and Term Set directly, bypassing the need for a Content Type and freeing up the control to be used just about anywhere (see the hyperlink below for more information about this component).
Two slightly awkward bits of information are required: the Term Store ID (exposed as the SSPList property) and the Term Set ID (exposed as the TermSetList property). How do you get these?
Open up Central Admin and go to your Managed Metadata service - you'll see the Term Store ID exposed in the URL. If you navigate the Term Store to the Term Set you want to use and open up something like the IE Dev Toolbar you can step through the HTML structure to the LI tag of the Term Set and the ID attribute of the LI tag is your Term Set ID.
Here's some more information from Microsoft on the Web Tagging Control and how to use it: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.taxonomy.taxonomywebtaggingcontrol.aspx
Thursday, 31 March 2011
Google +1
If you like the "Like" function that sites like Facebook implement then you may be interested to know that Google will be launching its own version over the coming weeks in an attempt to make their search results more social.
From what I understand at present, you need to have a Google profile to be able to use the facility, but you'll be able to recommend sites that appear in search results - and at some point in the future offer the same functionality within your public-facing websites.
Read more about it here: http://googlewebmastercentral.blogspot.com/2011/03/introducing-1-button.html?utm_source=feedburner&utm_medium=email&utm_campaign=Feed%3A+blogspot%2FamDG+%28Official+Google+Webmaster+Central+Blog%29
From what I understand at present, you need to have a Google profile to be able to use the facility, but you'll be able to recommend sites that appear in search results - and at some point in the future offer the same functionality within your public-facing websites.
Read more about it here: http://googlewebmastercentral.blogspot.com/2011/03/introducing-1-button.html?utm_source=feedburner&utm_medium=email&utm_campaign=Feed%3A+blogspot%2FamDG+%28Official+Google+Webmaster+Central+Blog%29
Tuesday, 22 March 2011
SharePoint 2010 - Reducing Page Load Size
Having gone round-and-round in circles looking to disprove (and prove to myself) Chris O'Brien's blog article about removing unwanted and unnecessary JavaScript files from SharePoint 2010 when a public-facing website is accessed anonymously, I've basically satisfied myself that there doesn't appear to be any better way to do this.
As Chris explains, SharePoint 2010 includes a whole host of JavaScript files which can bloat a page quite considerably. In a content author scenario, this isn't so much of a problem as the website will be accessed within an local and closed environment, more-often-than-not, and over the larger bandwidth that usually accompanies this it doesn't always pose a problem. As soon as a website becomes public-facing, there are many more considerations which not only include bandwidth, but also client capabilities.
Any work that can be done to reduce page size is always worthy of consideration. In light of this, referring to Chris here again, Microsoft introduced the Script On Demand framework in SharePoint 2010 to attempt to mitigate the page size. Problem is the scripts still get downloaded, and quite a few, despite this.
To solve it, Chris came up with a way to remove unnecessary JavaScript files from the list of registered files (which are stored in the HttpContext) but has had to use reflection to achieve this....it's not nice, but seems to be the only way to make it possible.
Here's his article explaining how - definitely worth consideration if performance is a concern.
As Chris explains, SharePoint 2010 includes a whole host of JavaScript files which can bloat a page quite considerably. In a content author scenario, this isn't so much of a problem as the website will be accessed within an local and closed environment, more-often-than-not, and over the larger bandwidth that usually accompanies this it doesn't always pose a problem. As soon as a website becomes public-facing, there are many more considerations which not only include bandwidth, but also client capabilities.
Any work that can be done to reduce page size is always worthy of consideration. In light of this, referring to Chris here again, Microsoft introduced the Script On Demand framework in SharePoint 2010 to attempt to mitigate the page size. Problem is the scripts still get downloaded, and quite a few, despite this.
To solve it, Chris came up with a way to remove unnecessary JavaScript files from the list of registered files (which are stored in the HttpContext) but has had to use reflection to achieve this....it's not nice, but seems to be the only way to make it possible.
Here's his article explaining how - definitely worth consideration if performance is a concern.
Continuing on from what Chris has said, another way to reduce the number of server requests is to use the ASP.NET ScriptManager which gets added to the masterpage and combine your own scripts into a single one. for instance you may be loading the base jQuery library as well as one or more plug-ins, plus your own custom JavaScript. Here's how:
<asp:ScriptManager id="ScriptManager" runat="server" EnablePageMethods="false" EnablePartialRendering="true" EnableScriptGlobalization="false" EnableScriptLocalization="true">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="<%$SPUrl:~sitecollection/Style Library/Scripts/scriptfile_001.js%>" ScriptMode="Release" />
<asp:ScriptReference Path="<%$SPUrl:~sitecollection/Style Library/Scripts/scriptfile_002.js%>" ScriptMode="Release" />
<asp:ScriptReference Path="<%$SPUrl:~sitecollection/Style Library/Scripts/scriptfile_003.js%>" ScriptMode="Release" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="<%$SPUrl:~sitecollection/Style Library/Scripts/scriptfile_001.js%>" ScriptMode="Release" />
<asp:ScriptReference Path="<%$SPUrl:~sitecollection/Style Library/Scripts/scriptfile_002.js%>" ScriptMode="Release" />
<asp:ScriptReference Path="<%$SPUrl:~sitecollection/Style Library/Scripts/scriptfile_003.js%>" ScriptMode="Release" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
Remember that doing this removes your files from the list of files used by the ScriptLink control.
Monday, 21 March 2011
JavaScript and CSS Combining
Well, there's always someone out there who's gone the extra mile...
If you want to know how to combine JavaScript and CSS into a single file and yuo're looking at performance of public-facing websites, this guy's written an article showing how it can be done - and not only that, it's without code:
http://blogs.msdn.com/b/shivap/archive/2007/05/01/combine-css-with-js-and-make-it-into-a-single-download.aspx
Nice idea but might make managing a little more complicated - okay if you have a small development team perhaps.
If you want to know how to combine JavaScript and CSS into a single file and yuo're looking at performance of public-facing websites, this guy's written an article showing how it can be done - and not only that, it's without code:
http://blogs.msdn.com/b/shivap/archive/2007/05/01/combine-css-with-js-and-make-it-into-a-single-download.aspx
Nice idea but might make managing a little more complicated - okay if you have a small development team perhaps.
Friday, 18 March 2011
Deploying SharePoint 2007 Solutions - Read-Only Attributes
If, for some strange and mystical reason, you have tinkered with the files delivered by a Solution Package to a server and set them to "read-only" [gasps with shock and horror] they will cause the Solution Package to fail when it's retracted, leaving artefacts effectively orphaned on the server.
It means you have to reset the files to remove the "read-only" attribute, redeploy the Solution and start again.
Just as a note, even when files are set to "read-only" when you're creating Solution Package (i.e. straight from TFS) the files are delivered with that attribute removed, probably for exactly the reasons above...
It means you have to reset the files to remove the "read-only" attribute, redeploy the Solution and start again.
Just as a note, even when files are set to "read-only" when you're creating Solution Package (i.e. straight from TFS) the files are delivered with that attribute removed, probably for exactly the reasons above...
Thursday, 17 March 2011
User Solutions (a.k.a Sandboxed Solutions), Web Parts and Web Controls
It's kind of disheartening when you look at User Solutions (which is how I'll refer to Sandboxed Solutions), especially if you're looking to use User Solutions to deliver some components that you're intending to hard-bake into a page layout (for instance).
Before I go any further explaining why, here's a nice short article giving a very brief overview of User Solutions from Wictor Wilen:
http://www.wictorwilen.se/Post/Understanding-the-SharePoint-2010-Sandbox-limitations.aspx
Ok, so off we go...
Recently I had an opportunity to look into User Solutions in more detail and found quite a few harsh limitations. My foray into this world came about while I was trying to add a Web Control into a page layout - i.e. hard-baked into the HTML. I had given myself the brief to use User Solutions to deliver all the code to see just how it could be achieved. Needless to say, after trying to add the assembly references into the page layout I tested the page, only to get an error stating that the assembly couldn't be found. Curious, I thought - so I tried and checked and re-checked my code and references to make sure I hadn't got it wrong but it was all to no avail. Then I read Wictor's article and found another article (http://www.elumenotion.com/Blog/Lists/Posts/Post.aspx?ID=109) which is when I had a moment of clarity and understanding.
It was an "of course" moment: user solutions run in their own process - the User Code Worker Process (a.k.a Sandbox Worker Process) outside of the IIS worker process. Pages are initially parsed by the IIS worker process and therefore any references to assemblies are also parsed at this point. Due to the way User Solution assemblies are used, the IIS worker process doesn't really know about them and therefore cannot resolve them - hence the error I was getting.
So that was that: but how can I add a Web Control that's delivered by User Solution into a page layout? Is it even possible? Well, in stepped the SPUserCodeWebPart. To be honest, despite doing the SharePoint 2010 exams, there was no mention of this component - nothing during my revision came up and highlighted this Web Part and yet, to me, it appears to be a fundamental part of the User Solution architecture.
Any way, putting that to one side, I implemented a test Web Part I had written: very simple "Hello world" example and added the necessary properties (Solution ID, Assembly Full Name, and the Type Full Name) to the SPUserCodeWebPart wrapper and there it was - it worked. The reason is works is because of the way the SPUserCodeWebPart makes a call out to the User Code Worker Process which loads DLLs from a separate location to those loaded by the IIS worker process. Each User Code assembly comes with its own web.config file declaring it as a Safe Control (which is why you cannot reference the assembly directly on the page as the IIS worker process knows nothing about these files). The wrapping SPUserCodeWebPart asks the User Code Worker Process to load the declared assembly and type from the specified solution: the work process looks to check the information and then loads the type against the subset of the Microsoft.SharePoint framework. Any errors it encounters, it pumps back up the pipeline.
But what about properties?, I thought. Well, these can also be passed as follows using the SPUserCodeProperty class:
In my Web Part I exposed a string property called "MyValue" and using the SPUserCodeProperty I set that to be "Some text". My Web Part then used that to output in HTML. Simple. Trying a more complex type I tried a custom enumeration passing in the value as my enumeration name and that also worked.
Before this success (which hadn't come about until the tail-end of my investigations) I had decided to try to get a Web Control to work. By it's name alone you can determine that the SPUserCodeWebPart is for use with Web Parts. I did some looking around and reflection and found no alternative for Web Controls and so I tried to use the SPUserCodeWebPart control to wrap a Web Control. To ensure you don't go down the same route, this doesn't work - it appears that you can only use classes that inherit from WebPart and anything else simply won't work.
If I do come across a way to do it, I'll be back.
Before I go any further explaining why, here's a nice short article giving a very brief overview of User Solutions from Wictor Wilen:
http://www.wictorwilen.se/Post/Understanding-the-SharePoint-2010-Sandbox-limitations.aspx
Ok, so off we go...
Recently I had an opportunity to look into User Solutions in more detail and found quite a few harsh limitations. My foray into this world came about while I was trying to add a Web Control into a page layout - i.e. hard-baked into the HTML. I had given myself the brief to use User Solutions to deliver all the code to see just how it could be achieved. Needless to say, after trying to add the assembly references into the page layout I tested the page, only to get an error stating that the assembly couldn't be found. Curious, I thought - so I tried and checked and re-checked my code and references to make sure I hadn't got it wrong but it was all to no avail. Then I read Wictor's article and found another article (http://www.elumenotion.com/Blog/Lists/Posts/Post.aspx?ID=109) which is when I had a moment of clarity and understanding.
It was an "of course" moment: user solutions run in their own process - the User Code Worker Process (a.k.a Sandbox Worker Process) outside of the IIS worker process. Pages are initially parsed by the IIS worker process and therefore any references to assemblies are also parsed at this point. Due to the way User Solution assemblies are used, the IIS worker process doesn't really know about them and therefore cannot resolve them - hence the error I was getting.
So that was that: but how can I add a Web Control that's delivered by User Solution into a page layout? Is it even possible? Well, in stepped the SPUserCodeWebPart. To be honest, despite doing the SharePoint 2010 exams, there was no mention of this component - nothing during my revision came up and highlighted this Web Part and yet, to me, it appears to be a fundamental part of the User Solution architecture.
Any way, putting that to one side, I implemented a test Web Part I had written: very simple "Hello world" example and added the necessary properties (Solution ID, Assembly Full Name, and the Type Full Name) to the SPUserCodeWebPart wrapper and there it was - it worked. The reason is works is because of the way the SPUserCodeWebPart makes a call out to the User Code Worker Process which loads DLLs from a separate location to those loaded by the IIS worker process. Each User Code assembly comes with its own web.config file declaring it as a Safe Control (which is why you cannot reference the assembly directly on the page as the IIS worker process knows nothing about these files). The wrapping SPUserCodeWebPart asks the User Code Worker Process to load the declared assembly and type from the specified solution: the work process looks to check the information and then loads the type against the subset of the Microsoft.SharePoint framework. Any errors it encounters, it pumps back up the pipeline.
But what about properties?, I thought. Well, these can also be passed as follows using the SPUserCodeProperty class:
In my Web Part I exposed a string property called "MyValue" and using the SPUserCodeProperty I set that to be "Some text". My Web Part then used that to output in HTML. Simple. Trying a more complex type I tried a custom enumeration passing in the value as my enumeration name and that also worked.
Before this success (which hadn't come about until the tail-end of my investigations) I had decided to try to get a Web Control to work. By it's name alone you can determine that the SPUserCodeWebPart is for use with Web Parts. I did some looking around and reflection and found no alternative for Web Controls and so I tried to use the SPUserCodeWebPart control to wrap a Web Control. To ensure you don't go down the same route, this doesn't work - it appears that you can only use classes that inherit from WebPart and anything else simply won't work.
If I do come across a way to do it, I'll be back.
Thursday, 10 March 2011
Using the Xslt List View Web Part for Cross-site Views
As you know, if you try to add a web part to a page, some of the options for web parts allow you to add a view of the lists contained in the current site. That's great, but what if you need to add a view of a list that exists elsewhere in the site?
Well, fortunately, this is possible in SharePoint 2010 with the aid of the new Xslt List View Web Part. This web part is a change from the old List View Web Part or Data View Web Part in that it uses XSLT to generate the view you see of columns and headers etc. It's also less hassle to create a view of a list that exists elsewhere in the site.
This is how...
- Go to the site where the list you want to use exists and add the web part for it to a page in that site
- In SharePoint Designer (SPD) open the page and, using the ribbon, select Web Part Options. On the right you will see an option for "Site Gallery" which saves the web part file to the Web Part Gallery (you cannot export it directly through the UI)
- You will be prompted with a question about whether you want the web part view of the list to be relative to the site in which the web part exists or to remain fixed on the current list it is set up to view. Make sure you make the choice that means the web part file is saved and remains fixed to the current list
- You can now safely remove the web part from the page and go to the site in which you want to add the view
- Using SPD (it must be SPD), open the page you want and add the saved web part from the Web Part Gallery
- Check in....your work is done
One note is that the view you get doesn't appear to get updated if you change the source list view.
Anonymous Access and SharePoint
I've recently done some work creating a some public-facing websites using SharePoint 2010. Needless to say, in the scenario of a website that anyone can access we have to obviously rely on anonymous access rather than forcing every user to somehow register and log in.
As a result of this doing work and experiencing some of the pain involved, it's safe to say that there are some areas of SharePoint 2010 that don't quite deliver what you would expect - in fact, one could go so far as to use the word "flake-y" in hushed tones.
Let me set the scenario of Problem One:
In SharePoint 2007, one aspect of content management is how content should be displayed to a content author as opposed to a content viewer. On one hand, the content author may want to see everything in the same way the viewer should see it, however, this is not always true or possible. A content author may have to configure any number of additional options and choices that the viewer of a page will never have sight of so therefore, the balance between author and viewer is slightly skewed.
In light of this skewed balance, we have often implemented different views for an author of a page and a viewer. How we do this in a single page layout, fortunately, has become quite simple by using the misnamed Edit Mode Panel that comes with SharePoint. The Edit Mode Panel has a Page Display Mode property which can be set to Edit or Display and it's this very important control that helps us out and works perfectly.
Moving on to SharePoint 2010 and the same situation, we start to put the Edit Mode Panel in everywhere to help us out - CSS, Field Controls, maybe a bit of JavaScript etc. Everything looks fine until we start looking in anonymous mode. Where's everything gone?
Well looking here: http://svengillis.blogspot.com/2010/06/inconvenient-editmodepanel-control.html and here: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.publishing.webcontrols.editmodepanel.aspx (at the comments) quickly tells us there's a problem. Oh dear.
So, moving on to the Problem Two:
One new aspect of SharePoint 2010 is the ability to use LINQ to SharePoint. You un-wrap SPMetal and generate your classes based on your site structure and libraries, and start writing your LINQ queries. Off you go creating your code without a care in the world. And then, anonymous mode again and you start getting some errors. You have a search and dig up some interesting links:
http://blog.mastykarz.nl/sharepoint-2010-linq-doesnt-support-anonymous-users/ and http://jcapka.blogspot.com/2010/05/making-linq-to-sharepoint-work-for.html
So what do you do? Go back to CAML or hack your way around the problem by storing, destroying and then reinstating the HttpContext. Oh dear.
Another one to chalk up, Problem Three:
You want to use the Client Object Model to get some list items back. Fortunately, this isn't something we've had to commercially deploy yet - although perhaps only a matter of time, but is none-the-less another anonymous-mode quirk. I wrote a blog article explaining the issue here:
http://whats-the-sharepoint.blogspot.com/2011/03/anonymous-access-using-sharepoint-2010.html and how to work around it. But once again, it's an anonymous-related issue that unless you do some digging you enter into blindly.
Oh dear, Microsoft.
Here's a more recent one, Problem Four:
Take a Content Query Web Part and use it to display items from a Picture Library or Document Library. Everything is fine. Swap into anonymous mode and suddenly the page is littered with errors and Correlation IDs. “What's going on?”, I hear you ask.
Waldek Mastykarz to the rescue again: http://blog.mastykarz.nl/inconvenient-sharepoint-2010-content-query-web-part-anonymous-access/ on the basis of a find by Paul Galvin here: http://www.mstechblogs.com/paul/sp-2010-cqwp-document-libraries-and-anonymous-users
Now, obviously, you have to add two additional fields to the Common View Fields property of the Content Query Web Part to resolve it, and they are SetDocumentIconUrlAndOnClickString and DocumentIconImageUrl, of course.
Thanks again, Microsoft, and oh dear again.
Last of all, Problem Five:
This isn't so much a problem as a question about how to work around an issue that appears to have been inherited from SharePoint 2007. As you already know there were multiple JavaScript files required in 2007, and with the added Ribbon, there are many more. In anonymous mode, the Ribbon isn't display and therefore its supporting JavaScript doesn't need to be included either. Enter the Script-on-demand framework (SOD). As Chris O'Brien points out, this doesn't resolve everything (and perhaps doesn't resolve "anything"):
http://www.sharepointnutsandbolts.com/2011/01/eliminating-large-js-files-to-optimize.html
It's a hack, an elegant one at that, and does get around the problem that is presented to us. Despite the scripts only being required "on demand" they are still loaded, even for anonymous users. So, hopefully, for the last time we utter: "oh dear, Microsoft".
In Conclusion
My advice is to watch out for anonymous access and go into it open eyed as it's an unavoidable part of public-facing websites. If you don't, you will see it as nasty and dirty and leave you with a feeling of being soiled, rueing the day you ignored my warning. Also, don't be fooled: there are five problems listed here, four of which will relate to every public-facing website we develop, however, there are more. I know of two more problems: one related to Style Library access in anonymous sites which causes the client browser to re-request resources instead of using its cache, and the second related to the Ribbon still being displayed in anonymous mode instead of being completely hidden. I wouldn't be surprised if there were more.
As a result of this doing work and experiencing some of the pain involved, it's safe to say that there are some areas of SharePoint 2010 that don't quite deliver what you would expect - in fact, one could go so far as to use the word "flake-y" in hushed tones.
Let me set the scenario of Problem One:
In SharePoint 2007, one aspect of content management is how content should be displayed to a content author as opposed to a content viewer. On one hand, the content author may want to see everything in the same way the viewer should see it, however, this is not always true or possible. A content author may have to configure any number of additional options and choices that the viewer of a page will never have sight of so therefore, the balance between author and viewer is slightly skewed.
In light of this skewed balance, we have often implemented different views for an author of a page and a viewer. How we do this in a single page layout, fortunately, has become quite simple by using the misnamed Edit Mode Panel that comes with SharePoint. The Edit Mode Panel has a Page Display Mode property which can be set to Edit or Display and it's this very important control that helps us out and works perfectly.
Moving on to SharePoint 2010 and the same situation, we start to put the Edit Mode Panel in everywhere to help us out - CSS, Field Controls, maybe a bit of JavaScript etc. Everything looks fine until we start looking in anonymous mode. Where's everything gone?
Well looking here: http://svengillis.blogspot.com/2010/06/inconvenient-editmodepanel-control.html and here: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.publishing.webcontrols.editmodepanel.aspx (at the comments) quickly tells us there's a problem. Oh dear.
So, moving on to the Problem Two:
One new aspect of SharePoint 2010 is the ability to use LINQ to SharePoint. You un-wrap SPMetal and generate your classes based on your site structure and libraries, and start writing your LINQ queries. Off you go creating your code without a care in the world. And then, anonymous mode again and you start getting some errors. You have a search and dig up some interesting links:
http://blog.mastykarz.nl/sharepoint-2010-linq-doesnt-support-anonymous-users/ and http://jcapka.blogspot.com/2010/05/making-linq-to-sharepoint-work-for.html
So what do you do? Go back to CAML or hack your way around the problem by storing, destroying and then reinstating the HttpContext. Oh dear.
Another one to chalk up, Problem Three:
You want to use the Client Object Model to get some list items back. Fortunately, this isn't something we've had to commercially deploy yet - although perhaps only a matter of time, but is none-the-less another anonymous-mode quirk. I wrote a blog article explaining the issue here:
http://whats-the-sharepoint.blogspot.com/2011/03/anonymous-access-using-sharepoint-2010.html and how to work around it. But once again, it's an anonymous-related issue that unless you do some digging you enter into blindly.
Oh dear, Microsoft.
Here's a more recent one, Problem Four:
Take a Content Query Web Part and use it to display items from a Picture Library or Document Library. Everything is fine. Swap into anonymous mode and suddenly the page is littered with errors and Correlation IDs. “What's going on?”, I hear you ask.
Waldek Mastykarz to the rescue again: http://blog.mastykarz.nl/inconvenient-sharepoint-2010-content-query-web-part-anonymous-access/ on the basis of a find by Paul Galvin here: http://www.mstechblogs.com/paul/sp-2010-cqwp-document-libraries-and-anonymous-users
Now, obviously, you have to add two additional fields to the Common View Fields property of the Content Query Web Part to resolve it, and they are SetDocumentIconUrlAndOnClickString and DocumentIconImageUrl, of course.
Thanks again, Microsoft, and oh dear again.
Last of all, Problem Five:
This isn't so much a problem as a question about how to work around an issue that appears to have been inherited from SharePoint 2007. As you already know there were multiple JavaScript files required in 2007, and with the added Ribbon, there are many more. In anonymous mode, the Ribbon isn't display and therefore its supporting JavaScript doesn't need to be included either. Enter the Script-on-demand framework (SOD). As Chris O'Brien points out, this doesn't resolve everything (and perhaps doesn't resolve "anything"):
http://www.sharepointnutsandbolts.com/2011/01/eliminating-large-js-files-to-optimize.html
It's a hack, an elegant one at that, and does get around the problem that is presented to us. Despite the scripts only being required "on demand" they are still loaded, even for anonymous users. So, hopefully, for the last time we utter: "oh dear, Microsoft".
In Conclusion
My advice is to watch out for anonymous access and go into it open eyed as it's an unavoidable part of public-facing websites. If you don't, you will see it as nasty and dirty and leave you with a feeling of being soiled, rueing the day you ignored my warning. Also, don't be fooled: there are five problems listed here, four of which will relate to every public-facing website we develop, however, there are more. I know of two more problems: one related to Style Library access in anonymous sites which causes the client browser to re-request resources instead of using its cache, and the second related to the Ribbon still being displayed in anonymous mode instead of being completely hidden. I wouldn't be surprised if there were more.
Anonymous Access using the SharePoint 2010 Client Object Model
One of the nice things about the client object model is the ability to retrieve items using ECMAScript and thereby making the UX of a public-facing website more dynamic and interesting. Items can be retrieved dynamically from lists within SharePoint 2010 and displayed in interesting ways, customisnig the whole interface if necessary with dynamic XHTML and CSS. However, this is all very well and good in a world of an Intranet / Extranet where users are authenticated, but in the world where you have Anonymous access enabled, the story is not quite the same.
While client-side object model calls do work, they do not work when your user is anonymous. The queries just do not run at all. The reason is quite simply because of a deliberate restriction applied to specific client object model calls in SharePoint 2010.
"What's the point of that?", I hear you say. Well, SharePoint rightly secures certain operations of the client object model to only allow authenticated users. I say "rightly so" because allowing the ability to query items in a list could pose a security threat or breach of data protection (for instance), especially if the list contains sensitive data. The client object model restrictions (of which there are more than one) are applied at a Web Application level which, admittedly, doesn't allow for a great deal of granularity, and can be viewed using the ClientCallableSettings.AnonymousRestrictedTypes property of an SPWebApplication which will display restrictions applied to SPList, SPWeb and SPSite client object model queries. Looking at a default installation of SharePoint you will see that there is a restriction imposed on the GetItems method of SPList objects - it's this that prevents the GetItems method from being used. You can get a list of the restrictions by running the following Powershell script:
Fortunately, the position is not untenable and SharePoint 2010, with the help of Powershell, allows us to remove the restriction and thereby allow any client object model calls by Anonymous Users to work successfully. Here's the Powershell script to remove the GetItems restriction:
It's useful that it's there, but does mean there is a need to balance security against required access. Allowing the GetItems is against all SPList objects across a single Web Application. This means that all client object model calls against any SPList will work within that single Web Application. While this is great it does mean that you do not have the granularity you may prefer in order to only allow a specific list the ability to be queried in this way (bit of a "sledge-hammer-to-crack-a-nut" approach). Therefore, when allowing this, bear in mind it is one-size-fits-all and you will need to ensure that a proper security model is followed for lists that contain sensitive data which may mean breaking inheritence in sites that allow access to anonymous users, in other words, being choosy about what you allow access to.
While client-side object model calls do work, they do not work when your user is anonymous. The queries just do not run at all. The reason is quite simply because of a deliberate restriction applied to specific client object model calls in SharePoint 2010.
"What's the point of that?", I hear you say. Well, SharePoint rightly secures certain operations of the client object model to only allow authenticated users. I say "rightly so" because allowing the ability to query items in a list could pose a security threat or breach of data protection (for instance), especially if the list contains sensitive data. The client object model restrictions (of which there are more than one) are applied at a Web Application level which, admittedly, doesn't allow for a great deal of granularity, and can be viewed using the ClientCallableSettings.AnonymousRestrictedTypes property of an SPWebApplication which will display restrictions applied to SPList, SPWeb and SPSite client object model queries. Looking at a default installation of SharePoint you will see that there is a restriction imposed on the GetItems method of SPList objects - it's this that prevents the GetItems method from being used. You can get a list of the restrictions by running the following Powershell script:
param($Identity)
if(-not $Identity) { $Identity = read-host -prompt "Site Collection"}
$site = Get-SPSite -Identity $Identity
Write-Host "Processing..."
$wa = $site.WebApplication
$wa.ClientCallableSettings.AnonymousRestrictedTypes
Fortunately, the position is not untenable and SharePoint 2010, with the help of Powershell, allows us to remove the restriction and thereby allow any client object model calls by Anonymous Users to work successfully. Here's the Powershell script to remove the GetItems restriction:
param($Identity)
if(-not $Identity) { $Identity = read-host -prompt "Site Collection"}
$site = Get-SPSite -Identity $Identity
Write-Host "Processing..."
$wa = $site.WebApplication
Write-Host "Removing the GetItems restriction..."
$wa.ClientCallableSettings.AnonymousRestrictedTypes.Remove([Microsoft.SharePoint.SPList], "GetItems")
$wa.Update()
Write-Host "The operation was completed sucessfully"
It's useful that it's there, but does mean there is a need to balance security against required access. Allowing the GetItems is against all SPList objects across a single Web Application. This means that all client object model calls against any SPList will work within that single Web Application. While this is great it does mean that you do not have the granularity you may prefer in order to only allow a specific list the ability to be queried in this way (bit of a "sledge-hammer-to-crack-a-nut" approach). Therefore, when allowing this, bear in mind it is one-size-fits-all and you will need to ensure that a proper security model is followed for lists that contain sensitive data which may mean breaking inheritence in sites that allow access to anonymous users, in other words, being choosy about what you allow access to.
Subscribe to:
Posts (Atom)