WCF and WF in "Orcas"

The wheels of evangelism never stop rolling.  Just a few months ago I was blogging that .NET 3.0 was released.  I've been busy since then, and now I can talk about some of that.  Today, the March CTP of Visual Studio "Orcas" was released to the web.  You can get your fresh hot bits here.  Samples will be coming shortly. Thom has a high level summary here.

UPDATE: Wendesday, 2/28/2007 @ 11pm.  The readme file is posted here, a few minor corrections have been made to the caveats below.

More updates... corrections to another caveat (a post-build event is required to get the config to be read).

A Couple of Minor Caveats

Since this is a CTP, it's possible that sometimes the wrong bits end up in the right place at the wrong time. Here are a few things to be aware of (not intended to be a comprehensive list):

  • Declarative Rules in Workflows:  There is an issue right now where the .rules file does not get hooked into the build process correctly.
    • Solution: Use code conditions, or load declarative rules for policy activiites using the RulesFromFile activity available at the community site
  • WF Project templates are set to target the wrong version: As a result, trying to add assemblies that are 3.0.0.0 or greater will not be allowed.
    • Solution: Right click the project, select properties, and change the targeted version of the framework to 3.0.0.0 or 3.5.0.0
  • A ServiceHost may not read config settings because the app config does not get copied to the bin (update: only on server 2003):  You will get an exception that "no application endpoints can be found"
    • Add the following Post Build Event "copy “$(ProjectDir)\app.config” $(TargetName).config "
    • Solution: For the time being, configure the WorkflowServiceHost in code (using AddServiceEndpoint() and referencing the WorkflowRuntime property to configure any services on the workflow runtime
    • This also means that a number of the workflow enabled services samples will not work out of the box.  Replace the config based approach with the code based approach and you will be fine.  I will try to post modified versions of these to the community site shortly.
  • WorkflowServiceHost exception on closing: You will get an exception that "Application image file could not be loaded... System.BadImageFormatException:  An attempt was made to load the program with an incorrect format"
    • Solution: Use the typical "These are not the exceptions you are looking for" jedi mind trick.  Catch the exception and move along in your application, as if there is nothing to see here.
  • Tools from the Windows SDK that you've come to know and love, like SvcConfigEditor and SvcTraceViewer are not available on the VPC. 
    • Solution: Copy these in from somewhere else and they will work fine. The SvcConfigEditor will even pick up the new bindings and behaviors to configure the services for some of the new functionality.

The CTP is not something that is designed for you to go into production with, it's designed to let you explore the technology. There is no go-live license associated with this, it's for you to learn more about the technology. Since most of these issues have some work around, this shouldn't prevent you from checking these things out (because they are some kind of neat).

New Features In WF and WCF in "Orcas"

Workflow Enabled Services

We've been talking about this since we launched at PDC 2005.  There was a session at TechEd 2006 in the US and Beijing that mentioned bits and pieces of this.  One of the key focus areas is the unification of WCF and WF.  Not only have the product teams joined internally, the two technologies are very complementary.  So complementary that everyone usually asks "so how do I use WCF services here?" when I show a workflow demo.  That's fixed now! 

Workflow enabled services allow two key things:

  • Easily consume WCF services inside of a workflow
  • Expose a workflow as a WCF service

This is accomplished by the following additions built on top of v1:

  • Messaging Activities (Send and Receive)
    • With designer support to import or create contracts
  • WorkflowServiceHost, a derivation of ServiceHost
  • Behavior extensions that handles message routing and instantiation of workflows exposed via services.
  • Channel extensions for managing conversation context.

 The Send and Receive activites live inside of the workflow that we define.  The cool part of the Receive activity is that we have a contract designer, so you don't have to dive in and create an interface for the contract, you can just specifiy it right on the Receive activity, allowing you a "workflow-first" approach to building services. 

Once we've built a workflow, we need a place to expose it as a service.  We use the WorkflowServiceHost which is a subclass of ServiceHost in order to host these workflow enabled services.  The WorkflowServiceHost takes care of the nitty-gritty details of managine workflow instances, routing incoming messages to the appropriate workflow and performing security checks as well.  This means that the code required to host a workflow as a WCF service is now reduced to four lines of code or so.  In the sample below, we are not setting the endpoint info in code due to the issue mentioned above.

    1:  WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(MyWorkflow));
    2:  wsh.Open();
    3:  Console.WriteLine("Press <Enter> to Exit");
    4:  Console.ReadLine();
    5:  wsh.Close();

To support some of the more sophisticated behavior, such as routing messages to a running workflow, we introduce a new channel extension responsible for managing context.  In the simple case, this context just contains the workflowId, but in a more complicated case, it can contain information similar to the correlation token in v1 that allows the message to be delivered to the right activity (think three receives in parallel, all listing on the same operation).  Out of the box there is the wsHttpContextBinding and the netTcpContextBinding which implicitly support the idea of maintaining this context token.  You can also roll your own binding and attach a Context element into the binding definition.

The Send activity allows the consumption of a service, and relies on configuraiton to detemrine exactly how we will call that service.  If the service we are calling is another workflow, the Send activity and the Receive activity are aware of the context extensions and will take advantage of them. 

With the Send and Receive actiivty, it gets a lot easier to do workflow to workflow communicaiton, as well as more complicated messaging patterns. 

Another nice feature of the work that was done to enable this is that we know have the ability to easily support durable services.  These are "normal" WCF services written in code that utilize an infrastructure similar to the workflow persistence store in order to provide a durable storing of state between method calls.

As you can imagine, I'll be blogging about this a lot more in the future.

JSON / AJAX Support

While there has been a lot of focus on the UI side of AJAX, there still remains the task of creating the sources for the UI to consume.  One can return POX (Plain Old Xml) and then manipulate it in the javascript, but that can get messy.  JavaScript Object Notation (JSON) is a compact, text-based serialization of a JavaScript object.  This lets me do something like:

 var stuff = {"foo" : 78, "bar" : "Forty-two"};
document.write("The meaning of life is " + stuff.bar);

In WCF, we can now return JSON with a few switches of config.  The following config:

    1:  <service name="CustomerService">
    2:      <endpoint contract="ICustomers"
    3:        binding="webHttpBinding"
    4:        bindingConfiguration="jsonBinding"
    5:        address="" behaviorConfiguration="jsonBehavior" />
    6:  </service>
    7:   
    8:  <webHttpBinding>
    9:          <binding name="jsonBinding" messageEncoding="Json" />
   10:  </webHttpBinding>
   11:   
   12:  <behaviors>
   13:     <endpointBehaviors>
   14:          <behavior name ="jsonBehavior">
   15:            <webScriptEnable />
   16:          </behavior>
   17:      </endpointBehaviors>
   18:   </behaviors>

will allow a function like this:

    1:  public Customer[] GetCustomers(SearchCriteria criteria)
    2:  {
    3:     // do some work here
    4:     return customerListing;
    5:  }

to return JSON when called.  In JavaScript, I would then have an instance of a CustomerOrder object to manipulate.  We can also serialize from JavaScript to JSON so this provides a nice way to send parameters to a method.   So, in the above method, we can send in the complex object SearchCriteria from our JavaScript.  There is an extension to the behavior that creates a JavaScript proxy.  So, by referencing /js as the source of the script, you can get IntelliSense in the IDE, and we can call our services directly from our AJAX UI.

We can also use the JSON support in other languages like Ruby to quickly call our service and manipulate the object that is returned.

I think that's pretty cool.

Syndication Support

While we have the RSS Toolkit in V1, we wanted to make syndication part of the toolset out of the box.  This allows a developer to quickly return a feed from a service.  Think of using this as another way to expose your data for consumption.  We have introduced a SyndicationFeed object that is an abstraction of the idea of a feed that you program against.  We then leave it up to config to determine if that is an ATOM or RSS feed (and, would it be WCF if we didn't give you a way to implement a custom encoding as well?)  So this is cool if you just want to create a simple feed, but it also allows you to create a more complicated feed that has content that is not just plain text.  For instance, the digg feed has information about the submission, the flickr feed has info about the photos.  Your customer feed may want to have an extension that contains the customer info that you will allow your consumers to have access to.  The SyndicationFeed object allows you to create these extensions and then the work of encoding it to the specific format is taken care of for you.  So, let's seem some of that code (note, this is from the samples above):

    1:  public SyndicationFeed GetProcesses()
    2:  {
    3:      Process[] processes = Process.GetProcesses();
    4:   
    5:      //SyndicationFeed also has convenience constructors
    6:      //that take in common elements like Title and Content.
    7:      SyndicationFeed f = new SyndicationFeed();            
    8:   
    9:      //Create a title for the feed
   10:      f.Title = SyndicationContent.CreatePlaintextTextSyndicationContent("Currently running processes");
   11:      f.Links.Add(SyndicationLink.CreateSelfLink(OperationContext.Current.IncomingMessageHeaders.To));
   12:   
   13:      //Create a new RSS/Atom item for each running process
   14:      foreach (Process p in processes)
   15:      {
   16:          //SyndicationItem also has convenience constructors
   17:          //that take in common elements such as Title and Content
   18:          SyndicationItem i = new SyndicationItem();
   19:   
   20:          //Add an item title.
   21:          i.Title = SyndicationContent.CreatePlaintextTextSyndicationContent(p.ProcessName);
   22:   
   23:          //Add some HTML content in the summary
   24:          i.Summary = new TextSyndicationContent(String.Format("<b>{0}</b>", p.MainWindowTitle), TextSyndicationContentKind.Html);
   25:          
   26:          //Add some machine-readable XML in the item content.
   27:          i.Content = SyndicationContent.CreateXmlSyndicationContent(new ProcessData(p));
   28:   
   29:          f.Items.Add(i);
   30:      }
   31:   
   32:      return f;
   33:  }

And the config associated with this would be:

 

    1:  <system.serviceModel>
    2:    <services>
    3:      <service name="ProcessInfo">
    4:         <endpoint address="rss"
    5:             behaviorConfiguration="rssBehavior" binding="webHttpBinding"
    6:             contract="HelloSyndication.IDiagnosticsService" />
    7:        <endpoint address="atom"
    8:             behaviorConfiguration="atomBehavior" binding="webHttpBinding"
    9:             contract="HelloSyndication.IDiagnosticsService" />    
   10:      </service>
   11:    </services>
   12:    <behaviors>
   13:      <endpointBehaviors>
   14:        <behavior name="rssBehavior">
   15:          <syndication version="Rss20"/>
   16:        </behavior>
   17:        <behavior name="atomBehavior">
   18:          <syndication version="Atom10"/>
   19:        </behavior>
   20:      </endpointBehaviors>
   21:    </behaviors>  
   22:  </system.serviceModel>

This config will actually create an RSS and an ATOM endpoint.  The feed returned would have the process information embedded as: (in this case in ATOM)

    1:  <entry>
    2:    <id>fe1f1d2e-d676-417d-85bf-b7969dd07661</id>
    3:    <title type="text">devenv</title>
    4:     <summary type="html">&lt;b&gt;Conversations - Microsoft Visual Studio&lt;/b&gt;</summary>
    5:     <content type="text/xml">
    6:       <ProcessData xmlns="https://schemas.datacontract.org/2004/07/HelloSyndication" 
    7:              xmlns:i="https://www.w3.org/2001/XMLSchema-instance">
    8:              <PeakVirtualMemorySize>552157184</PeakVirtualMemorySize>
    9:              <PeakWorkingSetSize>146124800</PeakWorkingSetSize>
   10:              <VirtualMemorySize>456237056</VirtualMemorySize>
   11:        </ProcessData>
   12:     </content>
   13:  </entry>

We can also use the Syndication support to consume feeds!

    1:  SyndicationFeed feed = new SyndicationFeed();
    2:  feed.Load(new Uri("https://blogs.msdn.com/mwinkle/atom.xml"));
    3:  foreach (SyndicationItem item in feed.Items)
    4:  {
    5:     // process the feed here
    6:  }

In the case where there have been extensions to the feed, we can access those as the raw XML or we can attempt to deserialize into an object.  This is accomplished in the reverse of the above:

    1:  foreach (SyndicationItem i in feed.Items)
    2:  {
    3:      XmlSyndicationContent content = i.Content as XmlSyndicationContent;
    4:      ProcessData pd = content.ReadContent<ProcessData>();
    5:   
    6:      Console.WriteLine(i.Title.Text);
    7:      Console.WriteLine(pd.ToString());
    8:  }
  

HTTP Programming Support

In order to enable both of the above scenarios (Syndication and JSON), there has been work done to create the webHttpBinding to make it easier to do POX and HTTP programming.

Here's an example of how we can influence this behavior and return POX.  First the config:

    1:  <service name="GetCustomers">
    2:    <endpoint address="pox" 
    3:              binding="webHttpBinding" 
    4:              contract="Sample.IGetCustomers" />
    5:  </service>

Now the code for the interface:

    1:  public interface IRestaurantOrdersService
    2:  {
    3:     [OperationContract(Name="GetOrdersByRestaurant")]
    4:     [HttpTransferContract(Method = "GET")]  
    5:     CustomerOrder[] GetOrdersByRestaurant();  
    6:  }

The implementation of this interface does the work to get the CustomerOrder objects (a datacontract defined elsewhere).  And the returned XML is the datacontract serialization of CustomerOrder (omitted here for some brevity).  With parameters this gets more interesting as these are things we can pass in via the query string or via a POST, allowing arbitrary clients that can form URL's and receive XML to consume our services.

Partial Trust for WCF

I'm not fully up to date on all of the details here, but there has been some work done to enable some of the WCF functionality to operate in a partial trust environment.  This is especially important for situations where you want to use WCF to expose a service in a hosted situation (like creating a service that generates an rss feed off of some of your data).  I'll follow up with more details on this one later.

WCF Tooling

You now get a WCF project template that also includes a self hosting option (similar to the magic Visual Studion ASP.NET hosting).  This means that you can create a WCF project, hit F5 and have your service available.  This is another are where I will follow up later on.

Wrap Up

So, what now? 

  • Grab the bits
  • Explore the new features
  • Give  us feedback (through my blog or a channel9 wiki I am putting together now)! What works, doesn't work, what do you like, not like, etc.
  • Look forward to more posts, c9 videos and screencasts on the features in Orcas.