Coding makes me feel good RSS 2.0
# Monday, June 3, 2013
Big congratulations to the BizTalk Services team for launching the public preview of Windows Azure BizTalk Services today. Find out more here.

Monday, June 3, 2013 10:43:25 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] -

# Friday, March 22, 2013

Now that the tools for developing SharePoint and Office 2013 apps have RTM’d (see Soma's blog here) I wanted to look at a particular scenario that I think will be very common. The SharePoint app model provides the ability to add a workflow to an app. The trouble is when you do this, workflows in apps can only be associated with other resources in the same app. SharePoint workflows are generally started in three ways, through a list association, manually started or programmatically. I think association with a list/library is by far the most common. So this means that a workflow you add to an app can only be associated with a list that is also defined in the same app. This isn’t really what you want though. Why? Because deploying lists as part of an app is problematic. This is because lists contain data and if you undeploy your app the data will be lost. It’s very likely that a workflow will change at a different rate to a list instance and so there is a problem.

The solution to this is to *not* associate the workflow to a list in the app. Instead, think about keeping the list definitions and workflow definitions separate. Of course, the workflow still needs to be associated to a list somehow, and to do this, you can use the Handle App Installed event receiver. In this event receiver you can wire up a workflow to any list in any app web or host web you choose. However, it is not (currently) possible to have a workflow definition in one web and a list in another web and associate the two. Instead, the workflow definition must be deployed to the same location as the list.

There are therefore several steps to deploying workflows in apps:

1.  Implement your app, add your workflow, etc

2.  Implement the Handle App Installed event receiver. This will do the following:

a.       Obtain a reference to the app’s web – the source

b.      Obtain a reference to the target web (e.g. host web or another app web)

c.       Copy the workflow definition from the source to the target

d.      Associate the workflow now in the target to the list in the target

e.      Specify the events the workflow should trigger on, e.g. item added or updated

3. Deploy your app

All of this is possible because of the rich API that workflow manager provides. Let’s have a look at the code required to achieve all this. The first step is to obtain a reference to the contexts for the source and target locations as shown below.

string targetUrl = "http://targetlocationUrl"; // this is the location of the list/library to associate workflow with
string targetListTitle = "DemoList"; // this is the title of the list to associate with the workflow
ClientContext sourceContext = TokenHelper.CreateAppEventClientContext(properties, true);
           
sourceContext.Load(sourceContext.Web);
sourceContext.ExecuteQuery();
 
Uri sharepointUrl = new Uri(targetUrl);
 
string contextTokenString = properties.ContextToken;
 
HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name];
SharePointContextToken contextToken = TokenHelper.ReadAndValidateContextToken(contextTokenString, requestProperty.Headers[HttpRequestHeader.Host]);
 
string accessToken = TokenHelper.GetAccessToken(contextToken, sharepointUrl.Authority).AccessToken;
ClientContext targetContext = TokenHelper.GetClientContextWithAccessToken(sharepointUrl.ToString(), accessToken);

The next thing to do is get a reference to the workflow manager objects. These will allow access and manipulation of workflow artifacts via the API. There are three main services, deployment, instance and subscription. The first step is to obtain references to the target app web:

var tgtwsm = new Microsoft.SharePoint.Client.WorkflowServices.WorkflowServicesManager(targetContext, targetContext.Web);
targetContext.Load(tgtwsm);
targetContext.ExecuteQuery(); var tgtwds = tgtwsm.GetWorkflowDeploymentService();
var tgtwis = tgtwsm.GetWorkflowInstanceService();
var tgtwss = tgtwsm.GetWorkflowSubscriptionService();
targetContext.Load(tgtwds); targetContext.Load(tgtwis); targetContext.Load(tgtwss); var lists = targetContext.Web.Lists; targetContext.Load(lists); targetContext.ExecuteQuery();

Then the code below is used to do the same for the source app web:

var srcwsm = new Microsoft.SharePoint.Client.WorkflowServices.WorkflowServicesManager(sourceContext, sourceContext.Web);
sourceContext.Load(srcwsm);
sourceContext.ExecuteQuery();

var srcwds = srcwsm.GetWorkflowDeploymentService();
var srcwis = srcwsm.GetWorkflowInstanceService();
var srcwss = srcwsm.GetWorkflowSubscriptionService();
sourceContext.Load(srcwds);
sourceContext.Load(srcwis);
sourceContext.Load(srcwss);

sourceContext.ExecuteQuery();

Then the workflow in the app can be retrieved - it will have been deployed at this point as the event is fired after installation. Here I'm simply grabbing the first workflow I find as I know there will be only one.

var wDefCollection = srcwds.EnumerateDefinitions(false);
sourceContext.Load(wDefCollection);
sourceContext.ExecuteQuery();

Guid definitionId = Guid.NewGuid();
string wfName = string.Empty;

foreach (var wDefEnumerator in wDefCollection)
{
   var defDetails = wDefEnumerator.Id + " : " + wDefEnumerator.DisplayName + "\n";
   definitionId = wDefEnumerator.Id;
   wfName = wDefEnumerator.DisplayName;
}

With the workflow definition in hand, I can retrieve the workflow xaml and copy it to the target app web and then save and publish it.

srcwds.GetDefinition(definitionId);
var _readDef = srcwds.GetDefinition(definitionId);
sourceContext.Load(_readDef);
sourceContext.ExecuteQuery();
string wfXaml = _readDef.Xaml;

var workflow = new Microsoft.SharePoint.Client.WorkflowServices.WorkflowDefinition(targetContext);
workflow.Xaml = wfXaml;
workflow.DisplayName = wfName;
targetContext.Load(workflow);
tgtwds.SaveDefinition(workflow);
targetContext.ExecuteQuery();
tgtwds.PublishDefinition(workflow.Id);
targetContext.ExecuteQuery();

Finally, I need to wire up the workflow definition with the list on which I want it to fire. For this I use the subscription service. It's possible to specify the event types such as ItemUpdated to define on which list operations a workflow instance will be created. The main piece of information required is ID of the list itself of course. I'm also specifying the history and task lists that workflow instances are able to use. I'll come back to that point in a moment.

var wSub = new Microsoft.SharePoint.Client.WorkflowServices.WorkflowSubscription(targetContext);
historyList = targetContext.Web.Lists.GetByTitle("WorkflowHistoryList");
taskList = targetContext.Web.Lists.GetByTitle("Workflow Tasks");
List targetList = targetContext.Web.Lists.GetByTitle(targetListTitle);
targetContext.Load(targetList);
targetContext.Load(historyList);
targetContext.Load(taskList);
targetContext.Load(wSub);
targetContext.ExecuteQuery();

wSub.Name = targetListTitle + " - ItemAdded";
wSub.DefinitionId = workflow.Id;
wSub.EventTypes = new List<string>() { "ItemAdded" };
wSub.EventSourceId = targetList.Id;
wSub.SetProperty("HistoryListId", historyList.Id.ToString());
wSub.SetProperty("TaskListId", taskList.Id.ToString());
wSub.SetProperty("ListId", targetList.Id.ToString());
wSub.SetProperty("Microsoft.SharePoint.ActivationProperties.ListId", targetList.Id.ToString());
var wSubId = tgtwss.PublishSubscription(wSub);
targetContext.ExecuteQuery();

If you want to use workflow history list and workflow task list you probably need to create them in the target app web. This is because they just won't exist. In the code below I'm checking if they already exist and if not, I create them (this code should go just above the previous block).

List historyList = null;
List taskList = null;

foreach (var list in lists)
{
    if (list.Title == "WorkflowHistoryList")
    {
        historyList = targetContext.Web.Lists.GetByTitle("WorkflowHistoryList");
    }
    if (list.Title == "Workflow Tasks")
    {
        taskList = targetContext.Web.Lists.GetByTitle("Workflow Tasks");
    }
}
// check if the lists exist in host
if (null == historyList)
{ // create the lists in the host ListCreationInformation tgtHistoryList = new ListCreationInformation(); tgtHistoryList.Title = "WorkflowHistoryList"; tgtHistoryList.TemplateType = 140; List tgtHistory = targetContext.Web.Lists.Add(tgtHistoryList); tgtHistory.Description = "This list instance is used for workflow History items."; tgtHistory.Update(); targetContext.ExecuteQuery(); } if (null == taskList) { ListCreationInformation tgtTaskList = new ListCreationInformation(); tgtTaskList.Title = "Workflow Tasks"; tgtTaskList.TemplateType = 171; List tgtTask = targetContext.Web.Lists.Add(tgtTaskList); tgtTask.Description = "This list instance is used for workflow Task items."; tgtTask.ContentTypesEnabled = true; ContentTypeCollection cts = targetContext.Web.AvailableContentTypes; targetContext.Load(cts); targetContext.ExecuteQuery(); ContentType ct = cts.GetById("0x0108003365C4474CAE8C42BCE396314E88E51F"); targetContext.Load(ct); targetContext.ExecuteQuery(); tgtTask.ContentTypes.AddExistingContentType(ct); tgtTask.Update(); targetContext.ExecuteQuery(); }
For this to compile, you must add a reference to Microsoft.SharePoint.Client.WorkflowServices.dll.

Next, add a workflow to the app, doesn't matter what you call it or what it does. During the creation process, make it a list workflow and un-check the box so that you don't associate it with anything. Then add a list, accept the defaults and call it DemoList. With the code above, what will happen is that the workflow will get deployed and wired up to the list programatically. In this example I'm just using the same source and target but you can change the target URL to whatever you like. In this way the workflow can be deployed in an app for convenience (one main reason being that you can develop workflows in Visual Studio without a local instance of SharePoint) and deploy to wherever you like.

With the code above in the Handle App Installed event receiver I can now deploy and test out the app. When the app installs my event receiver fires and copies it to the target URL I specify, wiring up to the list I provide in the code.

Navigating to the list (using a url in the form https://myO365namespace.sharepoint.com/workflowdeploydemo/lists/demolist) I see this:

Adding a new item we can see the workflow association and the instance executing:



This will bring up the workflows as shown below:


So, the workflow has executed and should've written out a line to the history list as my workflow just had a single activity WriteToHistory in it. Pointing the browser at this list (in the form https://myO365namespace.sharepoint.com/WorkflowDeployDemo/Lists/WorkflowHistoryList) and voila:

And that's it. How to deploy workflows in an app and associate them with the thing you really want to associate them with :-)
Friday, March 22, 2013 11:13:17 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
workflow | sharepoint
# Tuesday, October 30, 2012
I'm here, ready, registered and waiting. There's an enormous tent on the football pitches on campus that's clearly got something to do with it. Oh yeah, and as you can see below, it's raining :-)

Tuesday, October 30, 2012 12:13:48 AM (GMT Standard Time, UTC+00:00)  #    Comments [1] -

# Thursday, October 25, 2012
woohoo! Sharepoint, Office, etc etc. 

Thursday, October 25, 2012 11:40:10 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2] -

# Sunday, October 21, 2012
Great night, thanks to Michael Stephenson, a sell-out crowd apparently. Thanks for the opportunity Michael. The slides I presented on Wednesday can be downloaded here.

Sunday, October 21, 2012 11:36:43 AM (GMT Daylight Time, UTC+01:00)  #    Comments [4] -

# Friday, October 12, 2012
Just a shameless advert for me that I'll be talking on Azure Integration Services at Michael's Stephenson's user group on the 17th October. 

You can register here - http://ukcsugoct2012.eventbrite.com/

Should be fun, hope you can make it.
Friday, October 12, 2012 2:13:00 PM (GMT Daylight Time, UTC+01:00)  #    Comments [5] -

# Wednesday, October 3, 2012
So many people seem to run into problems with claims based authentication (CBA), SAML, STS's etc etc. It's complicated. The one thing I think that makes it easier to understand is a code sample and to that end I'm posting a solution to a question I solved recently - "how do I authenticate from a non-Windows or non Windows-authenticated client (e.g. not NTLM) with SharePoint Online?". I'm providing the VB.NET example here first (to illustrate the process), but I'll post others shortly. The point is that with just straight HTTP you can accomplish this, no WIF, no WCF etc, making this code easy to port to other languages and platforms. 

Firstly, this approach was inspired by this post describing the necessary steps. So, I won't go over that here, but simply provide a working solution for you. As I mentioned, the fun part is next, doing this from other platforms. First up will be typescript.

If you want to try this out, you'll need three pieces of info

1. Your user name
2. Your password
3. Your domain e.g. affinus which is then used in various calls to <domain>.sharepoint.com e.g. affinus.sharepoint.com

You can set up a brand new shiny domain quickly and easily, for free, with no credit card here.

Enjoy.

Imports System.Net
Imports System.Security.Cryptography.X509Certificates
Imports System.Net.Security

Module CBAuthentication

    Function ValidateRemoteCertificate(sender As Object, cert As X509Certificate, chain As X509Chain, er As SslPolicyErrors) As Boolean
        Debug.WriteLine("Trusting X509Certificate '" + cert.Subject + "'")
        Return True
    End Function

    Sub Main()

        Dim userName = "<user>@<domain>.onmicrosoft.com"
        Dim password = "<password>"
        Dim domain = "<domain>.sharepoint.com"
        Dim myDomain = "http://" + domain + "/_forms/default.aspx?wa=wsignin1.0"
        Dim myEP = "https://" + domain + "/TeamSite/"
        Const msoURL = "https://login.microsoftonline.com/extSTS.srf"
        Dim listEndpoint = "http://" + domain + "/TeamSite/_vti_bin/lists.asmx"

        Dim SAMLAuth = <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"
                           xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                           <s:Header>
                               <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
                               <a:ReplyTo>
                                   <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
                               </a:ReplyTo>
                               <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
                               <o:Security s:mustUnderstand="1"
                                   xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
                                   <o:UsernameToken>
                                       <o:Username><%= userName %></o:Username>
                                       <o:Password><%= password %></o:Password>
                                   </o:UsernameToken>
                               </o:Security>
                           </s:Header>
                           <s:Body>
                               <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
                                   <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
                                       <a:EndpointReference>
                                           <a:Address><%= myEP %></a:Address>
                                       </a:EndpointReference>
                                   </wsp:AppliesTo>
                                   <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
                                   <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
                                   <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
                               </t:RequestSecurityToken>
                           </s:Body>
                       </s:Envelope>

        Dim httpRequest As HttpWebRequest = System.Net.WebRequest.Create(msoURL)

        ServicePointManager.ServerCertificateValidationCallback = AddressOf ValidateRemoteCertificate

        httpRequest.Method = "POST"
        Dim requestStream = httpRequest.GetRequestStream()
        Dim xmlDoc As System.Xml.XmlDocument = New System.Xml.XmlDocument()

        Dim stream As System.IO.MemoryStream = New System.IO.MemoryStream()
        SAMLAuth.Save(stream)
        stream.Seek(0, 0)
        xmlDoc.Load(stream)

        requestStream.Write(System.Text.Encoding.UTF8.GetBytes(xmlDoc.OuterXml), 0, xmlDoc.OuterXml.Length)

        Dim response As HttpWebResponse = httpRequest.GetResponse()
        xmlDoc.Load(response.GetResponseStream())

        ' get the token, look for t=
        Dim token As String = xmlDoc.OuterXml.Substring(xmlDoc.OuterXml.IndexOf(">t=") + 1)
        token = token.Substring(0, token.IndexOf("</wsse:BinarySecurityToken>"))

        ' POST the token
        httpRequest = System.Net.WebRequest.Create(myDomain)
        httpRequest.Method = "POST"
        httpRequest.Expect = AcceptRejectRule.None
        httpRequest.AllowAutoRedirect = False
        httpRequest.CookieContainer = New CookieContainer()

        requestStream = httpRequest.GetRequestStream()
        requestStream.Write(System.Text.Encoding.UTF8.GetBytes(token), 0, token.Length)

        ' get the auth cookie headers
        response = httpRequest.GetResponse()

        Dim cookieHeader As String = response.Headers("Set-Cookie")

        Dim fedAuth = response.Cookies("FedAuth").Value
        Dim rtFa = response.Cookies("rtFa").Value

        ' make web service request with the cookie
        httpRequest = System.Net.WebRequest.Create(listEndpoint)

        httpRequest.CookieContainer = New CookieContainer()
        httpRequest.CookieContainer.Add(New Cookie("FedAuth", fedAuth, "/", domain))
        httpRequest.CookieContainer.Add(New Cookie("rtFa", rtFa, "/", domain))

        httpRequest.ContentType = "text/xml; charset=utf-8"
        httpRequest.Method = "POST"

        requestStream = httpRequest.GetRequestStream()

        Dim envelope = <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                           xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                           xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
                           <soap:Body>
                               <GetList xmlns="http://schemas.microsoft.com/sharepoint/soap/">
                                   <listName>Documents</listName>
                               </GetList>
                           </soap:Body>
                       </soap:Envelope>

        stream = New System.IO.MemoryStream()
        envelope.Save(stream)
        stream.Seek(0, 0)
        xmlDoc.Load(stream)

        requestStream.Write(System.Text.Encoding.UTF8.GetBytes(xmlDoc.OuterXml), 0, xmlDoc.OuterXml.Length)

        response = httpRequest.GetResponse()
        xmlDoc.Load(response.GetResponseStream())

        ' write out the list retrieved
        System.Console.WriteLine(xmlDoc.OuterXml)

    End Sub

End Module

Wednesday, October 3, 2012 11:50:19 PM (GMT Daylight Time, UTC+01:00)  #    Comments [3] -

# Tuesday, September 18, 2012

Warning, warning!! this a non-technical post. For a change.

Well, this morning just wasn’t the same. Why? No Chris Moyles and his motley crew. After what is undoubtedly a lifetime (8 years) in TV and radio they’ve left. But this isn’t the only thing that makes me sad, in fact it’s not even the main thing.

It’s the fact that the BBC has decided, and told me very clearly that I’m just too old to listen to Radio 1. That not only makes me sad, but makes me mad too. The ignominy really annoys me, how dare they? That I should feel some shame that I’ve been told that in my early 40s, I am too old to listen to someone not much younger than me on a station that employed the late, great John Peel until the end.  In new music we trust? I guess that goes for DJs too… 

I actually like the Guardian’s commentary the best  (I paraphrase) – ‘Peppa Pig Magazine isn’t staffed by 3-year olds’. You get the point.

What is clear is that the BBC don’t know what to do with him. With over a year left on his contract I should imagine Chris is somewhere between laughing and crying right now. I can only assume we’re all now paying for him to sit at home for the next 18 months enjoying his gardening leave.  Thanks again, BBC.

Chris loves radio, a self-confessed geek. I can related to that. For an industry that’s increasingly staffed with wanna be TV presenters travelling through, that’s very rare. For Radio 1 not to be able to find him a new home immediately, that’s madness.  I know he wasn’t everyone’s cup of tea – but who is? I didn’t agree with everything he said either, but I don’t agree with everything anybody says. So what?

So, 8 million listeners (give or take). I guess their logic is that by ‘lowering’ the average age to 29 they will be able to attract *more* young people? I would be amazed if it turns out like that. More likely they’ll get to that ratio by losing people in bulk and hoping they lose more older than younger (big gamble). I still fondly remember Steve Wright in the Afternoon. I was in my early twenties, and I know for sure Steve was a damn sight older than me. He wasn’t cool either. Did I care? Not a jot.

So I wouldn’t mind betting that all this will create the next big idea  - working out how to increase the listening figures when they tank. What’s more important – listeners or younger listeners? Go figure…

I’m sure they made Chris feel old this week. But they made me feel old too. And for that, shame on you BBC.  I guess it’s time for me to move to Radio 2, and make myself feel *really* old.

Good luck Chris wherever you end up next, you’ve done a fantastic job and you should all indeed feel very proud. Just lay off the game shows will you? You’re bigger than that (metaphorically speaking).

Normal service will resume shortly…

Tuesday, September 18, 2012 8:22:18 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2] -
Media
# Saturday, June 9, 2012

I'm back. And I'm back on azure hosting my blog using some of the cool new features unveiled on Thursday. I'll be tidying things up around here and adding content from my original blog as I get used to living here instead. Stay tuned, I might even go crazy and add some new stuff too.

Saturday, June 9, 2012 10:29:57 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] -

Categories
Archive
<December 2014>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2014
Jon Fancey
Sign In
Statistics
Total Posts: 9
This Year: 0
This Month: 0
This Week: 0
Comments: 17
Themes
Pick a theme:
All Content © 2014, Jon Fancey
DasBlog theme 'Business' created by Christoph De Baene (delarou)