As you can see from the image, the ID attribute of the DIV is a GUID, and this GUID is the Feature ID. Simple.
Showing posts with label SharePoint 2010. Show all posts
Showing posts with label SharePoint 2010. Show all posts
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
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
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.
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)