In response to this post, Anderson raised the following question.
Definitely too hard and not service-oriented. I like the idea of sending it via the headers or some other such implementation, but that would also require implementation on the other side in the form of an agreed-upon place and format of the context information or a custom binding implementation that you either distribute or have the other side of the fence reimplement. The first time I did this I spent days working on it saying to myself "surely you don't have to do this... surely you don't have to tell the target service which *activity* you want it to talk to next". But you do. It makes me sad inside. matt: Our general goal is not to introduce more misery into the world. At this point in time, doing duplex requires the explicit management of the context token. We're working to make it better, so hopefully around PDC time, you will no longer be sad inside. One thing I've not seen implemented yet in a demo. I really like the feature of State Machine super states and substates. The ability to take a group of 3 states and put them in another state called "Cancellable" or some other such interrupty business function is really kickass. Let's say you are in state "Processing" which has an event-driven that listens for the service method "FinishedProcessing". "Processing" is also in a superstate called "Cancellable" with an event-driven that listens for "CancelProcess". How could you implement this when you have to tell the other service about the context identifier of the target receive activity? Do you have to send both contexts? I fear your answer will be yes. matt: In that case, provided you are not listening for the same operation in two places, you can get away with grabbing the context token from either FinishedProcessing or CancelProcess and send that along. The context token will be the same fore either of them, it will only consist of the Workflow Instance Id. The Instance Id + the OperationName is the combination used to enqueue an inbound message, unless you've gotten clever as in the Conversations sample and told the Receive activities to create queues based on an additional conversation id.
Definitely too hard and not service-oriented. I like the idea of sending it via the headers or some other such implementation, but that would also require implementation on the other side in the form of an agreed-upon place and format of the context information or a custom binding implementation that you either distribute or have the other side of the fence reimplement.
The first time I did this I spent days working on it saying to myself "surely you don't have to do this... surely you don't have to tell the target service which *activity* you want it to talk to next". But you do. It makes me sad inside.
matt: Our general goal is not to introduce more misery into the world. At this point in time, doing duplex requires the explicit management of the context token. We're working to make it better, so hopefully around PDC time, you will no longer be sad inside.
One thing I've not seen implemented yet in a demo. I really like the feature of State Machine super states and substates. The ability to take a group of 3 states and put them in another state called "Cancellable" or some other such interrupty business function is really kickass.
Let's say you are in state "Processing" which has an event-driven that listens for the service method "FinishedProcessing". "Processing" is also in a superstate called "Cancellable" with an event-driven that listens for "CancelProcess". How could you implement this when you have to tell the other service about the context identifier of the target receive activity? Do you have to send both contexts? I fear your answer will be yes.
matt: In that case, provided you are not listening for the same operation in two places, you can get away with grabbing the context token from either FinishedProcessing or CancelProcess and send that along. The context token will be the same fore either of them, it will only consist of the Workflow Instance Id. The Instance Id + the OperationName is the combination used to enqueue an inbound message, unless you've gotten clever as in the Conversations sample and told the Receive activities to create queues based on an additional conversation id.
Also, it sounds like Anderson is delivering a talk tomorrow night in Dallas about WCF /WF integration. If you're in the area, head on over and check it out.
When we start doing this two way style of messaging, we now open up to start modeling some interesting business problems. In the previous post, you'll note that I did not include the code, because I mentioned we needed to be more clever in scenarios where we listen in parallel.
First, a brief diversion into how the Receive activity works. Everybody remembers the workflow queues, the technology that underlies all communication between a host and a workflow instance. The Receive activity works by creating a queue that the WorkflowServiceHost (specifically the WorkflowOperationInvoker) will use to send the message received off the wire into the workflow. Now, the Receive activity normally just creates a queue that is named the same as the operation the Receive activity is bound to. However, if we have two Receive activities listening for the same operation at the same time, no longer is a single queue useful to route responses back as we want to route to the correct Receive activity instance.
There is property on the Receive activity called ContextToken. Normally this is null in the simple case. However, when we want our Receive activity to operate in parallel, we need to indicate that it needs to be smarter when it creates a queue.
By setting this property (you can just type in a name, and then select the common owner all of the parallel receive's share. This will cause the Receive activity to create a queue named [OperationName] +[ConversationId], the conversation ID takes the form of a GUID, and is the second element inside a context token.
The sample that I show for this talk is simply the conversations sample inside the SDK. This is the sample to check out to understand all sorts of interesting ways to use the context tokens to model your processes.
Now, there are two conversation patterns here. One is the one shown above, which I refer to as an n-party conversation where n is fixed at design time. We can accomplish this with the parallel activity. The other is where n is arbitrary (imagine you send out to business partners stored in the database). The way to do this is to use the Replicator activity. The Replicator is a little known gem shipped in 3.0 that essentially gives you "ForEach" semantics. But, by flipping the ExecutionType switch to parallel, I now get the behavior of a parallel, but operating with an arbitrary n branches.
So, in order to enable conversations, we need to tell our receive activity to be a little smarter about how it generates its queue name, and then we simply follow the duplex pattern we discussed in the last two posts. Once we do that, we're in good shape to start modeling some more interesting communication patterns between multiple parties.
We can just make the patterns more interesting. One interesting one would be the combination of the long running work with cancellation and a Voting activity in order to coordinate the responses and allow for progress to be made when some of the branches complete (if I have 3 yes votes, I can proceed). The power of building composite activities is that it gives me a uniform programming model (and a single threaded one to boot) in order to handle the coordination of different units of work. Get out there and write some workflows :-)
So, we've seen in part 1 how to manage context, we saw in part 2 how we can take that basic knowledge to do duplex messaging. Once we start doing duplex work, there are some interesting patterns, and the first one is one that we like to call "long running work". Why are we interested in this? Well, as you probably know, the execution of a workflow is single threaded (this is a feature, not a bug). We also don't have a mechanism to force the workflow to be "pinned" in memory. What this means is that things like the asynchronous programming model (APM), can't be used, since there isn't a guarantee that there will be something to call back when we are done. What this means is that the send activity can not take advantage of the APM to be more thread friendly.
We may want to do things in parallel, like this
If each of these branches takes 3 seconds, the whole of this workflow will complete in about 9 seconds. The general expectation is that in parallel, this would happen at the length of the longest branch + some minor delta for overhead. The trouble is, APM programming is tricky, especially relative to the layout above.
In order to model APM style service calls, but allowing for the service operations to be extremely long running, where extremely is defined as "long enough to where I would want to be able to persist." The approach then is to model this as disjoint send and receive activities.
One intermediate step is to simply use one way messaging, but the problem there is that in a lot of cases, I'm looking for some information being sent back to me.
I'll hold off on the code for the above, the fact we are listening in parallel for the same operation requires us to be a little more clever.
Let's look first at our contract, and then our service implementation:
1: namespace Long_Running_Work
2: {
3: [ServiceContract]
4: public interface ILongRunningWork
5: {
6: [OperationContract]
7: string TakeAWhile(int i);
8:
9: [OperationContract(IsOneWay = true)]
10: void OneWayTakeAWhile( int i);
11:
12: [OperationContract(IsOneWay = true)]
13: void TakeAWhileAndTellMeLater(IDictionary<string,string> contextToken, int i);
14: }
15:
16:
17: [ServiceContract]
18: public interface IReverseContract
19: {
20: [OperationContract(IsOneWay = true)]
21: void TakeAWhileAndTellMeLaterDone(string s);
22: }
23:
24: }
And now for the implementation of these;
3: public class Service1 : ILongRunningWork
4: {
5:
6: public Service1()
7: {
9: }
10:
11: #region ILongRunningWork Members
12:
13: public string TakeAWhile(int i)
14: {
15: Console.WriteLine("Starting TakeAWhile");
16: System.Threading.Thread.Sleep(new TimeSpan(0, 0, 3));
17: return i.ToString();
18: }
19:
20:
21:
22: public void OneWayTakeAWhile( int i)
23: {
24: Console.WriteLine("Starting One Way TakeAWhile");
25: System.Threading.Thread.Sleep(new TimeSpan(0, 0, 3));
26: Console.WriteLine("Ending One Way TakeAWhile");
27:
28:
29: }
30:
31:
32: public void TakeAWhileAndTellMeLater(IDictionary<string, string> context, int i)
33: {
34: Console.WriteLine("Received the context Token");
35: System.Threading.Thread.Sleep(new TimeSpan(0, 0, 3));
36: Console.WriteLine("Need to Message Back Now {0}", i.ToString());
37: // could investigate a more useful pooling of these if we
38: // really wanted to worry about perf
39: IReverseContractClient ircc = new IReverseContractClient(
40: new NetTcpContextBinding(),
41: new EndpointAddress("net.tcp://localhost:10003/ReverseContract")
42: );
43: IContextManager icm = ircc.InnerChannel.GetProperty<IContextManager>();
44: icm.SetContext(context);
45: ircc.TakeAWhileAndTellMeLaterDone(i.ToString());
46: }
47:
48:
49:
50: #endregion
51: }
52:
53: public class IReverseContractClient : ClientBase<IReverseContract>, IReverseContract
54: {
55: public IReverseContractClient() : base(){}
56: public IReverseContractClient(System.ServiceModel.Channels.Binding binding, EndpointAddress address) : base(binding, address) { }
57:
58: #region IReverseContract Members
59:
60:
61:
62: public void TakeAWhileAndTellMeLaterDone(string s)
63: {
64: base.Channel.TakeAWhileAndTellMeLaterDone(s);
65: }
66:
67: #endregion
68: }
69:
70: }
Basically, we sit around and wait. You'll also note in the TakeAWhileAndTellMeLater, we take in a context token (similar to our previous approach), and we will use that to new up a client at the end and call back in after setting the context. Look at lines 39-44 above. The nice thing about this is that my above workflow client can actually go idle, persist, and react to a message being delivered later on.
One thing to note is that one should not place a delay between any of the Send and Receives. This could cause the workflow to go idle, which may allow you to miss messages. This is generally considered, a bad thing. The reason this occurs is that the WorkflowOperationInvoker will use EnqueueOnIdle which means that when teh workflow goes idle, the message will be enqueued. If the queue hasn't been created by the Receive activity, the message will not get delivered.
For the final workflow above (the TakeAWhileAndTellMeLater workflow), I will need to spin this up in a WorkflowServiceHost (a la the Duplex Sample in part 2).
using (WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(CallLongRunningComponents.WorkflowWithmessaging))) { wsh.AddServiceEndpoint( typeof(Long_Running_Work.IReverseContract), new NetTcpContextBinding(), "net.tcp://localhost:10003/ReverseContract" ); // don't forget to open up the wsh WorkflowRuntime wr = wsh.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime; wsh.Open(); WorkflowInstance wi = wr.CreateWorkflow( typeof(CallLongRunningComponents.WorkflowWithmessaging)); wr.WorkflowCompleted += ((o, e) => waitHandle.Set()); wr.WorkflowIdled += ((o, e) => Console.WriteLine("We're idled")); wi.Start(); waitHandle.WaitOne(); }
Why do I think this is cool?
Two reasons:
call some services and wait:
1: Console.WriteLine("Press <enter> to execute APM approach");
2: Console.ReadLine();
3: waitHandle = new AutoResetEvent(false);
4: Stopwatch sw = new Stopwatch();
5: sw.Start();
6: lrwc = new WorkflowHost.ServiceReference1.LongRunningWorkClient();
7: lrwc.BeginTakeAWhile(1, HandleClientReturn, "one");
8: lrwc.BeginTakeAWhile(2, HandleClientReturn, "two");
9: lrwc.BeginTakeAWhile(3, HandleClientReturn, "three");
10: lrwc.BeginTakeAWhile(4, HandleClientReturn, "four");
11: while (!areDone)
12: {
13: System.Threading.Thread.Sleep(25);
15: Console.WriteLine("APM approach compelted in {0} milliseconds", sw.ElapsedMilliseconds);
16: Console.WriteLine("All Done, press <enter> to exit");
17: Console.ReadLine();
Ignore the busy wait on line 11, I should use a waithandle here but was having trouble getting it to work correctly (this is hard code).
The callback and respective state:
1: static ServiceReference1.LongRunningWorkClient lrwc;
2: static Int32 countOfFinished = 0;
3:
4: static void HandleClientReturn(IAsyncResult result)
6: string s = (string)result.AsyncState;
7: string resultString = lrwc.EndTakeAWhile(result);
8: Console.WriteLine("received {0}", resultString);
9: if (Interlocked.Increment(ref countOfFinished) == 4)
10: {
11: areDone = true;
12: }
13: }
I have had some people say that line 9 should use Interlocked.CompareExchange in order to do this correctly, but the point is that this is tricky code, that modeling in WF is pretty nice. [ignoring for the moment the work required to realize the assumption that we can make the service message back.]
Martin posted an interesting question here on my last post:
<quote> The first thing that we need to do in order to enable this duplex messaging to occur is that the "client" workflow has to explicitly provide its context token to the service so that the service can address the appropriate instance of the client workflow. Note, in the real world, you'll probably need to supply more than just the context token, you will need some address and binding information. </quote> Shouldn't we have this built into a custom binding? (or an extra binding element) So with every call from the client the (WF)context information is included. And the developer is not required to follow a (artificial) state machine. Note, at the time, when the service calls back, the endpoint (and the binding) of the client may have changed... So we may need dynamic name-endpoint resolution (sounds like DNS?) Martin
<quote>
The first thing that we need to do in order to enable this duplex messaging to occur is that the "client" workflow has to explicitly provide its context token to the service so that the service can address the appropriate instance of the client workflow.
Note, in the real world, you'll probably need to supply more than just the context token, you will need some address and binding information.
</quote>
Shouldn't we have this built into a custom binding? (or an extra binding element) So with every call from the client the (WF)context information is included. And the developer is not required to follow a (artificial) state machine.
Note, at the time, when the service calls back, the endpoint (and the binding) of the client may have changed... So we may need dynamic name-endpoint resolution (sounds like DNS?)
Martin
The question here is generally also asked as "wait, why do I need to explicitly provide a context token and change the contract to have this context thing?" This is a common question, as changing the contract to reflect implementation details is generally a no-no. There's one part I left out as well, so let me add that here:
In the real world, one may also wish to not change the contract (or may not have the ability to). In that case, we still need to explicitly provide the context token and endpoint information in order to allow me to call back. There are a few ways to do this, of varying complexity and implication:
This is also the same approach one could take with using an implicit, or content based correlation scheme. In that case, you create an intermediary that is responsible for translating message contents into the "addressing" information for the instance. That intermediary can be a service, it could be a channel, and once you put that intermediary in place, you are free to do as sophisticated or as simple work as possible.
A continuation of my series of demos from my advanced workflow services talk. Here we focus on duplex message exchange patterns.
Duplex messaging is something that we model at the application level (as opposed to the infrastructure level) because we want to model that message exchange at the level of the application. Here's some scenarios where I could use duplex messaging:
One may ask the question, "But, what about the wsHttpDualBinding, or WCF duplex bindings." That's a valid question, but it's important to point out that those bindings are really used to describe the behavior of a given proxy instance (and associated service artifacts). When my proxy dies, or the underlying connection goes away, I lose the ability for the service to call back to me. Additionally, this binds me to listen in the same way that I sent out the initial message.
By modeling this at the application layer, we do lose some of the "automagicity" of the WCF duplex behavior, but I get more flexibility, and I get the ability to sustain potentially repeated recycling of the services and clients. Also, you could imagine a service that I call that turns around and calls a third party service. That third party service could call back directly to the client that made the initial call. Note, once we start doing duplex communication (and we'll encounter this in part 4, conversations), is that the definition of "client" and "service" become a bit muddier.
So, to the code:
Let's look at the contract of the service:
[ServiceContract(Namespace ="http://microsoft.com/dpe/samples/duplex")] public interface IOrderProcessing { [OperationContract()] void SubmitOrder(string customerName, IDictionary<string, string> context); [OperationContract(IsOneWay = true )] void AddItem(OrderItem orderItem); [OperationContract(IsOneWay = true )] void CompleteOrder(); }
You'll note that on the SubmitOrder method, I pass in a context token. This is my callback correlation identifier, this is how I will figure out what instance on the client side I want to talk to. Now, I need to do some work to get the context token in order to send, so let's look at how we do this:
On the client side, on the first Send activity, let's hook the BeforeSend event.
Let's look at the implementation of GrabToken:
1: private void GrabToken(object sender, SendActivityEventArgs e)
3: ContextToSend = receiveActivity1.Context;
4: Console.WriteLine("Received token to send along");
5: }
6:
7: public static DependencyProperty ContextToSendProperty = DependencyProperty.Register("ContextToSend", typeof(System.Collections.Generic.IDictionary<string, System.String>), typeof(OrderSubmitter.Workflow1));
9: [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
10: [BrowsableAttribute(true)]
11: [CategoryAttribute("Parameters")]
12: public System.Collections.Generic.IDictionary<string, String> ContextToSend
13: {
14: get
15: {
16: return ((System.Collections.Generic.IDictionary<string, string>)(base.GetValue(OrderSubmitter.Workflow1.ContextToSendProperty)));
17: }
18: set
20: base.SetValue(OrderSubmitter.Workflow1.ContextToSendProperty, value);
21: }
First, note that on lines 7-22 we declare a dependency property call ContextToSend. Think of this simply as a bindable storage space. On line 3, we go and assign to that the value of receiveActivity1.Context. "But Matt, couldn't I just build a context token off the workflow ID?" You could, but you're only going to be correct in the "simple scenario." You can see we then take that ContextToSend, and pass that into the context parameter for the service operation. Always walk up and ask a Receive activity for its context token, don't try to build one on your own.
Now, on the service side, we need to extract that, and we need to apply the value to the send activity in the service workflow that needs to call back. We basically can do the reverse:
private void codeActivity1_ExecuteCode(object sender, EventArgs e) { //set callback context sendActivity1.Context = callbackContext; }
This is inside a code activity in the first receive activity. callbackContext is a dependency property that is bound to the inbound context on the Receive activity.
The final trick is that both workflows have to be hosted inside a WorkflowServiceHost. This makes sense for the "service" workflow, since it will be message activated. On the client side, we have to do a little bit of work in order to get to the workflow runtime to spin up a workflow instance. In the early betas, we had an easy way to get to the runtime, WorkflowServiceHost.WorkflowRuntime. In order to conform more with the extensibility of WCF, this has been moved to the extensions of the service host. We get there by:
1: static void Main(string[] args)
3: using (WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(Workflow1)))
5: Console.WriteLine("Press <ENTER> to start the workflow");
6: Console.ReadLine();
7: wsh.Open();
8: WorkflowRuntime wr = wsh.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime;
9: WorkflowInstance wi = wr.CreateWorkflow(typeof(Workflow1));
10: AutoResetEvent waitHandle = new AutoResetEvent(false);
11: wr.WorkflowCompleted += delegate { waitHandle.Set(); };
12: wr.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine("error {0}", e); waitHandle.Set(); };
13: wi.Start();
14: waitHandle.WaitOne();
15: Console.WriteLine("Workflow Completed");
16: Console.ReadLine();
On line 3, you'll see we new up a WorkflowServiceHost based on the service type (it will do this to find and open the respective endpoints). On line 8, we reach in and grab the WorkflowRuntimeBehavior and get the WorkflowRuntime, and we use that to create an instance of the workflow.
So, here's what we have done:
So, last week I wrapped up a conversation at TechReady, our internal conference, where I was talking about the integration between WF and WCF in .NET 3.5. This talk was somewhat bittersweet, it's the last conference where I'm scheduled to talk about WF 3.0/3.5, I'll start talking about WF 4.0 at PDC this fall.
There are a series of 4 demos that we'll talk about in this series:
I've gotten a lot of requests to post the code samples, so I want to do that here:
The goal of this sample is to show the way that the context channel works, and how to interact with it from imperative code.
1: public String returnValue = default(System.String);
2: public String inputMessage = default(System.String);
4: private void codeActivity1_ExecuteCode(object sender, EventArgs e)
6: returnValue = string.Format("first activity {0}", inputMessage);
7: Output(inputMessage + " Activity 1");
8: }
9:
10: private void Output(string message)
11: {
12: Console.WriteLine("Workflow {0} : Message {1}", this.WorkflowInstanceId, message);
14:
15: private void codeActivity2_ExecuteCode(object sender, EventArgs e)
16: {
17: returnValue = string.Format("second activity {0}", inputMessage);
18: Output(inputMessage + " Activity 2");
19: }
class IWorkflowClient : ClientBase<Intro1.IWorkflow1>, Intro1.IWorkflow1 { public IWorkflowClient() : base() { } public IWorkflowClient(Binding binding, EndpointAddress address) : base(binding, address) { } public string Hello(string message) { return base.Channel.Hello(message); } }
private static void CheckAndPrintContext(IContextManager icm) { if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count); if (null != icm) { if (icm.GetContext().Count > 0) { foreach (string xmlName in icm.GetContext().Keys) { Console.WriteLine("key : {0}", xmlName); Console.WriteLine("value : {0}", icm.GetContext()[xmlName]); } } } }
1: private static void DemoOne()
3: Console.WriteLine("Press Enter to Send a Message and reuse proxy");
4: // Console.ReadLine();
5: Debugger.Break();
6: IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
7: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
8: IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
9: if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
10: string s = iwc.Hello("message1");
11: Console.WriteLine("the service returned the message '{0}'", s);
12: CheckAndPrintContext(icm);
13: s = iwc.Hello("message2");
14: icm = iwc.InnerChannel.GetProperty<IContextManager>();
15: CheckAndPrintContext(icm);
16: Console.WriteLine("the service returned the message '{0}'", s);
17: Console.WriteLine("Press Enter to Continue");
1: // show this not working using a second client
2: private static void DemoTwo()
3: {
4: Console.WriteLine("Press Enter to Send a Message (it will break this time)");
5: //Console.ReadLine();
6: Debugger.Break();
7: IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
8: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
9: IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
10: if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
11: string s = iwc.Hello("message1");
12: Console.WriteLine("the service returned the message '{0}'", s);
13: CheckAndPrintContext(icm);
14: iwc = new IWorkflowClient(new NetTcpContextBinding(),
15: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
16: s = iwc.Hello("message2");
17: Console.WriteLine("the service returned the message '{0}'", s);
18: icm = iwc.InnerChannel.GetProperty<IContextManager>();
19: CheckAndPrintContext(icm);
20: Console.WriteLine("Press Enter to Continue");
1: // show this working with a second client by caching the context
2: private static void DemoThree()
4: Console.WriteLine("Press Enter to Send a Message (we'll cache the context and apply it to the new proxy)");
5: // Console.ReadLine();
14: IDictionary<string, string> context = icm.GetContext();
15: icm = null;
16: iwc = new IWorkflowClient(new NetTcpContextBinding(),
17: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
19: icm.SetContext(context);
20: s = iwc.Hello("message2");
21: Console.WriteLine("the service returned the message '{0}'", s);
22: icm = iwc.InnerChannel.GetProperty<IContextManager>();
23: CheckAndPrintContext(icm);
24: Console.WriteLine("Press Enter to Exit");
25:
26: }
So, what have we shown:
Late last night, the PDC team posted an additional "bunch" of sessions, including one I'm particularly interested in:
Extending Windows Workflow Foundation v.Next with Custom Activities Presenter: Matt Winkler Windows Workflow Foundation (WF) coordinates and manages individual units of work, encapsulated into activities. The next version of WF comes with a library of activities, including Database and PowerShell. Learn how to extend this library by encapsulating your own APIs with custom activities. See how to compose those basic activities into higher level units using rules, flowchart, or state machine control flow styles. Learn how to extend beyond WF control styles by building your own. Learn how to customize and re-host the workflow authoring experience using the new WF designer framework. Tags: Advanced, WF
Extending Windows Workflow Foundation v.Next with Custom Activities
Presenter: Matt Winkler
Windows Workflow Foundation (WF) coordinates and manages individual units of work, encapsulated into activities. The next version of WF comes with a library of activities, including Database and PowerShell. Learn how to extend this library by encapsulating your own APIs with custom activities. See how to compose those basic activities into higher level units using rules, flowchart, or state machine control flow styles. Learn how to extend beyond WF control styles by building your own. Learn how to customize and re-host the workflow authoring experience using the new WF designer framework.
Tags:
Advanced, WF
They also posted a number of other interesting Oslo sessions that folks might be interested in, these cover a wide range of the things that our group is doing:
"Oslo" provides a language for creating schemas, queries, views, and values. Learn the key features of the language, including its type system, instance construction, and query. Understand supporting runtime features such as dynamic construction and compilation, SQL generation, and deployment. Learn to author content for the "Oslo" repository and understand how to programmatically construct and process the content.
"Oslo" provides visual tools for writing data-driven applications and services. Learn how to provide a great experience over domain-specific schemas, and explore the basic user model, data-driven viewer construction, user-defined queries, and custom commands. See how the design experience itself is an "Oslo" application and is driven by content stored in the "Oslo" repository.
"Oslo" builds on Windows Workflow (WF) and Windows Communication Foundation (WCF) to provide a feature-rich middle-tier execution and deployment environment. Learn about the architecture of "Oslo" and the features that simplify deployment, management, and troubleshooting of workflows and services.
"Oslo" uses schematized data stored in the "Oslo" repository to drive the development and execution of applications and services. Tour the schemas and see how user-defined content can be created and related to them. Learn how to utilize platform schemas, such as worflow, services, and hosting. Also, learn how to extend the repository and how to use repository-extended SQL database services to support critical lifecycle capabilities such as versioning, security, and deployment.
It'll be a good time, can't wait to see ya there!