Showing posts with label User Solutions. Show all posts
Showing posts with label User Solutions. Show all posts

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);
}


/// <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());
}

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.

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.