Marquee de Sells: Chris's insight outlet for category 'spout' via ATOM 1.0 csells on twitter

You've reached the internet home of Chris Sells, who has a long history as a contributing member of the Windows developer community. He enjoys long walks on the beach and various computer technologies.




USB HD w/ OS/Apps + FolderShare = Portable Computing?

If you could boot Windows Vista from a USB drive large enough for the OS and apps, like this version of Linux, and log into FolderShare for your data, you could use any computer that allowing booting from USB into a temporary terminal (although device drivers would be a challenge).

0 comments




After the writing is done

Today Michael Weinhardt and I submitted the final chapters of "Windows Forms 2.0 Programming in C#" for review. Mike has worked his butt off for the last 20 months to bring this revision about, taking a fresh look at every single chapter, not only for updates to the technology, but also for story. The book is far, far better than it was without him and he deserves your love and praise when the book is released (hopefully in April of 2006).

Since this is Mike's first book, I imagine he thinks that most of the work is done. Here's the list of things I sent him that still needs doing:

  1. Mike will make changes to the chapters based on reviewer feedback, being careful to leave in change marks so I can see what he's changed
  2. I’ll review each chapter, approving or rejecting changes and making my own edits and comments and we'll repeat this ‘til we’re both happy
  3. We’ll send everything by our post-reviewer-comment-application reviewer and apply his feedback like steps 1-2
  4. I’ll update the preface after Thanksgiving, sending it by Mike and our final reviewer ‘til we're all happy (and in the process, you’ll put in your acknowledgements)
  5. We’ll each update the frontmatter.doc to include dedications
  6. I’ll update the TOC.doc to match our final TOC
  7. Mike will pull out all of the figures into separate bitmap files (.bmp)
  8. By 12/12, we’ll upload the complete final manuscript, both Word and PDF versions, and send it off to AW
  9. Hopefully AW will send it along to Betsy, my most favorite copy editor ever, and she’ll mark it up, sending Mike the marked up pages to approve or disapprove each copy edit
  10. AW will drop PDF versions of each of the final chapters to us, which we’ll submit change lists for
  11. We’ll ask for updated PDFs, ‘cuz the publisher traditionally only applies half of the feedback on each round, so we’ll repeat 10-11 ‘til they get them all
  12. Sometime in April of 2006, we’ll each get a big box of books with our names on the front cover, which indicates that they’re on their way to the book sellers, e.g. B&N, Amazon, Borders, etc. At this point, I’ll update the web site and post the sample code while Mike updates the app that tracks errata
  13. People will send us email telling us how much they like the book to butter us up before asking a technical question. We’ll answer each question to the best of our ability and ask each questioner to post a review on Amazon.com
  14. Some of the questioners will submit bugs in the book, which we’ll track in the errata database
  15. When a print run has been exhausted (the initial print run will be between 10K – 20K copies, with subsequent print runs between 3K – 5K copies), AW will ask us for a list of changes for the n+1th edition. For some reason, they’ll always ask for those changes less than a week before they need them
  16. AW will send us each a copy of the n+1th edition, which we’ll use to check that the errata we submitted was actually applied (for some reason, publishers don’t always apply all the changes we ask for), marking errata in the database as fixed in the n+1th edition
  17. Repeat 13-16 for 18-24 months ‘til everyone that wants a book on this technology has purchased it
  18. Get started on "Programming Windows Forms 3.0 in C#," repeating steps 1-17 after all of the chapters have been submitted for review

I figured Mike has had about 12 hours to enjoy being "done," so it was high time to crush his spirit. : )

0 comments




Have I missed anything?

I've noticed that various prolific bloggers feel the need to tell me that they're going to stop blogging for more than 24 hours. That's one of the things I love about RSS readers: no matter how long someone goes away, so long as I'm subscribed, I here their every utterance.

On the other hand, what I've come to hate about RSS readers is that nothing gets by me. Keeping track of absolutely everything was exhausting, so I went cold turkey. I've been off #R since I repaved my machine a coupla weeks ago. I still surf to slashdot.org about once/day, but that's it. No Scobelizer. No Don. No Ian. No Dilbert or Gizmodo or any of the 100s of other items in my OPML that I can't even remember. No RSS reader of any kind.

So far, I'm enjoying the extra time on my hands. Have I missed anything?

0 comments




What's a group of geeks called?

Sloth of bears.

Army of ants.

Murder of crows.

Tribe of monkeys.

Embarrassment of richest.

What about geeks? Conference? Shy? Intelligence? Slather?

P.S. this site on collective nouns has a bunch of fun ones. I think I like "smack of jellyfish" best...

0 comments




Testing ASP.NET 2.0 port of sb.com

Pay no attention to the may behind the curtain...

0 comments




The Most Amazing Consumer Experience

I just had the most amazing consumer experience and I just had to share it.

I was looking for the local Mom & Pop computer store in my area and I stumbled across www.puzzlecomputers.com. His site doesn't mention a store, but I left him a message asking if he could upgrade my computer or point me at the local Mom & Pop. Not only does he do that kind of work, but he came to my house, picked up the two computers I wanted to use as parts, e.g. case, RW-DVD, HD, etc, recommended and purchased the new motherboard, CPU, memory and quiet power supply, put them all together (including modifying my old proprietary Dell case to fit), brought the whole thing back to my house and then spent another hour installing another HD I had lying around. The specs on the parts were 1GB of RAM (upgradeable to 2GB), 2.8GHz AMD processor and ABIT motherboard with all the trimmings.

The whole thing, parts, labor, delivery, etc. cost $400. Plus, he hauled away the extra parts so I don't have to look at them. And when the drivers didn't install right away, he was available for phone support.

Highly recommended. I wish all my consumer experiences were like this one!

0 comments




WF Activity Context

I was playing around with a state machine workflow in WF and ran across something that I didn't except. Apparently if you have a an activity that can be other than a singleton, e.g. inside of a loop or as part of a state machine workflow, then an activity can not be accessed by it's workflow instance variable. For example, the following works just fine in a sequence workflow:

public partial class Workflow2 : SequentialWorkflow {
private void code1_ExecuteCode(object sender, EventArgs e) {
// Retrieve Comment property from typed event sink (generated by wca)
string comment = this.eventSink1.Comment;
    Console.WriteLine("Order approved w/ comment= {0}", comment);
  }
}

However, in the case where the activity can be executed more than once, it's my understand that each time through, there's a new activity object that's different from the named activity associated with the workflow. To get a hold of the current activity, you have to traverse the activity tree, which you can access in a code activity via the sender's parent property:

public partial class StateMachine1 : StateMachineWorkflow {
void code2_ExecuteCode(object sender, EventArgs e) {
// Retrieve Comment property from typed event sink (generated by wca)
    Code codeActivity = (Code)sender;
    IOrderService_Events.OrderApproved eventSink2 =
      (IOrderService_Events.OrderApproved)(codeActivity.Parent.Activities["eventSink1"]);
    string comment = eventSink2.Comment; // eventSink1.Comment will be unbound
    Console.WriteLine("order approved w/ comment= {0}", comment);
  }
}

I haven't figured out whether this makes sense to me yet, but that's how it is. You can get the code here.

0 comments




Typed WF Communications Details

If you downloaded the samples from my previous two forays into WF communications programming, you probably couldn't get them to build. The problem is that when I packaged things up, I removed the bin directories. I haven't figured out just why this causes everything to stop building, but with the help of Dennis Pilarinos, I rearranged the two projects into three:

  1. communications interface (CommunicationLibrary): the data exchange service interface definition, compiles into CommunicationLibrary.dll

  2. communications activities (WorkflowProject1): uses a pre-build step to generate the typed activities generated from the communications library using the wca tool, references CommunicationLibrary.dll and compiles into WorkflowProject1.dll (nice name, eh? : )

  3. application (EventSinkAndMethodInvoke2): the app that uses the typed activities, references both CommunicationLibrary.dll and WorkflowProject1.dll

It's only by separating the communications activities into a separate assembly that things can build and rebuild reliably. You can download the newly configured code here. Enjoy.

0 comments




I'm starting to speak in numbers

Someone posted an URL to a mailing list I'm on. My response to his post was "404." I bet you know what I meant w/o thinking about it. He knew and responded as if I'd written in English. This reminds me of the old joke about the guys that'd be around each other so long, they replaced back the same old jokes with numbers and make each other laugh. It's a funny old world.

0 comments




Web Services and Data Binding?

What does it mean to bind to a web service? It's not hard to think of taking the result of a web service and binding it as input to some set of controls, but what about changes to that data? Should data from web services be read-only, requiring an explicit call to add, remove or update or should their be some standard WS-CRUD standard? What do you nice folks do?

0 comments




More Workflow Communications Spelunking

Here. The one where I take Dennis's suggestion, try wca.exe with my workflow communications problems, and discover as many more problems as I solve.

0 comments




More Workflow Communciation Spelunking

After complaining about the inability to bind directly to event inputs, Dennis suggested I try the Workflow Communications Activity generator utility (wca.exe). If you point it at an assembly that contains interfaces decorated with the DataExchangeService attribute, it will generate typed invoke method and event sink activities for each method and event in the interface. To support this, I moved the communication interface, IOrderService, and types the interface depends on, i.e. Order and OrderEventArgs, to a separate library project and added the following post-build step:

"C:\Program Files\Microsoft SDks\Windows Workflow Foundation\wca.exe"
  "$(TargetPath)" /o:"$(SolutionDir)\EventSinkAndMethodInvoke2"

The reason I added this as a post-build step in the library project instead of as a pre-build stuff in the actual wf app, is because I want to have the types to program against whenever the library changes. However, either way, to get the new activities to show up on the toolbar, you have to build the application. Once I'd done that, I updated my workflow using the typed activities:

Unfortunately, just as I was capturing that screenshot, the Workflow Designer couldn't find the activities, so it dropped them from my workflow without so much as a "by your leave," reminding me where much of the early WinForms Designer.

However, two nice things result from these generated types. The first is that my design-time experience, either in the designer or in the XOML, is improved because I don't have to do a bunch of parameter binding:

<SequentialWorkflow
  x:Class="EventSinkAndMethodInvoke2.Workflow2"
  x:CompileWith="Workflow2.xoml.cs"
  ID="Workflow2"
  xmlns:x="Definition"
  xmlns="Activities">

  <ns0:CreateOrder
customer="Fabrikam"
    orderDescription="42&quot; Plasma TV"
    ID="createOrder1"
    MethodName="CreateOrder"
    InterfaceType="EventSinkAndMethodInvoke2.IOrderService"
    xmlns:ns0="IOrderService_Operations" />

  <ns1:OrderApproved
ID="orderApproved1"
    EventName="OrderApproved"
    InterfaceType="EventSinkAndMethodInvoke2.IOrderService"
    xmlns:ns1="IOrderService_Events" />

  <Code ExecuteCode="code1_ExecuteCode" ID="code1" />

  <Terminate
Error="*d2p1:ActivityBind(ID={orderApproved1};Path=Comment)"
ID="terminate1"
    xmlns:d2p1="ComponentModel" />

</SequentialWorkflow>

The other nice thing is that, because the typed event sink has properties that directly expose the event arguments, i.e. Comment and Order, instead of just via parameter bindings, I can bind to them in the b1 build of WF. This reduces my coupling, because the terminate activity doesn't know where it's getting it's input, and it takes away that "global variable" feel I had when I was binding parameters in and out of fields on the workflow itself. If I want to access the event sink's typed properties directly, I can do so, as shown in the code activity's handler:

public partial class Workflow2 : SequentialWorkflow {
  void code1_ExecuteCode(object sender, EventArgs e) {
    Console.WriteLine("Order: approved w/ comment= {0}", orderApproved1.Comment);
  }
}

I really like that typed activities are generated for me based on my data exchange service interfaces, but it's still a manual, multi-stage process today. I'd prefer that it happened automatically, like typed resources and settings in VS05. If that can't happen, I'd prefer to be able to bind directly to the parameter binding list that the generic event sink activity already knows about. At least that way, if there's a problem, my workflow doesn't get destroyed because of it.

The updated VS05b2, WF/WinFXb1 sample code is available for your enjoyment.

0 comments




Workflow Communication Spelunking

Here. The one where I figure out how to communicate into, out of and between WF activities, but not in a satisfying way.

0 comments




Workflow Communication Spelunking

I just an interesting week taking a crash course in Windows Workflow Foundation (formerly WWF, not just WF) programming. My big hang-up was, how do I communicate into, out of and between activities?

I started with Dennis's explanation of communications into and out of a workflow. The fundamental idea is that, once a workflow has started, you can't talk to it directly. Instead, you set up a communications interface that the workflow can use to talk to the host and that the workflow can use to watch for events from the host, e.g.

[DataExchangeService]
public interface IOrderService {
  void CreateOrder(string customer, string orderDescription);
  event EventHandler<OrderEventArgs> OrderApproved;
}

The interface is marked with the DataExchangeServer attribute to mark it as an interface suitable for host<->workflow communication, but other than that, it's a normal .NET interface. The host, i.e. the chunk of code that creates the WF runtime, implements data exchange service interfaces as singletons, e.g.

class OrderServiceImpl : IOrderService {
  Dictionary<Guid, Order> _workflowOrderMap = new Dictionary<Guid, Order>();

  public void CreateOrder(string customer, string desc) {
    _workflowOrderMap.Add(BatchEnvironment.CurrentInstanceId, new Order(customer, desc));
  }

  public void ApproveOrder(WorkflowInstance wf, string comment) {
    if (OrderApproved != null) {
      Guid wfId = wf.InstanceId;
      OrderApproved(null, new OrderEventArgs(wfId, _workflowOrderMap[wfId], comment));
    }
  }

  public event EventHandler<OrderEventArgs> OrderApproved;
}

With this implementation, the host is allowing the workflow to call the CreateOrder method (which we'll see it do later) and to subscribe to the OrderApproved event. The CreateOrder method uses its arguments to create an Order object and associate it with the ID of the currently executing workflow (available via BatchEnvironment.CurrentInstanceId). Remember, the service implementation is a singleton, but any number of workflows can call it, so when they do, we track information on a per workflow basis.

WF Question #1: How do I associate objects directly w/ a running workflow instead of tracking things in dictionaries?

The OrderApproved event is used to get information into the workflow.

In our scenario, imagine we're creating an order, approving it (w/ a comment) and logging the result. In my sample, I have a workflow imaginatively named "Workflow2" which captures this sequence:

The invoke method activity is bound to the CreateOrder method of the IOrderMethod method:

<InvokeMethodActivity
ID="invokeMethodActivity1"
  MethodName="CreateOrder"
  InterfaceType="EventSinkAndMethodInvoke2.IOrderService">
  <InvokeMethodActivity.ParameterBindings>
    <wcm:ParameterBinding ParameterName="customer" xmlns:wcm="ComponentModel">
      <wcm:ParameterBinding.Value>
        <?Mapping XmlNamespace="System" ClrNamespace="System" Assembly="mscorlib" ?>
        <ns0:String xmlns:ns0="System">Fabrikam</ns0:String>
      </wcm:ParameterBinding.Value>
    </wcm:ParameterBinding>
    <wcm:ParameterBinding ParameterName="orderDescription" xmlns:wcm="ComponentModel">
      <wcm:ParameterBinding.Value>
        <?Mapping XmlNamespace="System" ClrNamespace="System" Assembly="mscorlib" ?>
        <ns0:String xmlns:ns0="System">42" Plasma TV</ns0:String>
      </wcm:ParameterBinding.Value>
    </wcm:ParameterBinding>
  </InvokeMethodActivity.ParameterBindings>
</InvokeMethodActivity>

In this case, we're hard-coding the custom and order description fields, but in a real workflow, you'd take those as input parameters.

WF Question #2: How do you pass input parameters into a workflow?

WF Question #3: Is it legal XML to have a processing instruction in the middle of a file, e.g. <?Mapping...?>?

After the workflow creates the order, it waits for a human to approve it via an event sink activity:

<EventSinkActivity
ID="eventSinkActivity1"
  EventName="OrderApproved"
  InterfaceType="EventSinkAndMethodInvoke2.IOrderService">
  <EventSinkActivity.ParameterBindings>
    <wcm:ParameterBinding ParameterName="Comment" xmlns:wcm="ComponentModel">
      ...
    </wcm:ParameterBinding>
    <wcm:ParameterBinding ParameterName="Order" xmlns:wcm="ComponentModel">
      ...
    </wcm:ParameterBinding>
  </EventSinkActivity.ParameterBindings>
</EventSinkActivity>

The event sink waits for the host to fire an event, which has several interesting bits. The first interesting bit is the parameter names, which are bound to the public properties of the OrderEventArgs class passed to the OrderEvent event:

[Serializable]
public class OrderEventArgs : WorkflowMessageEventArgs {
  Order _order;
  public Order Order { get { return _order; } }

  string _comment;
  public string Comment { get { return _comment; } }

  public OrderEventArgs(Guid workflowInstanceId, Order order, string comment)
: base(workflowInstanceId) {
    _order = order;
    _comment = comment;
  }
}

Notice that the custom OrderEventArgs class derives from the WorkflowMessageEventArgs class and passes in the workflow instance ID. This is required so that the event can be routed to the appropriate workflow. Without it, you'll get the following illuminating error in beta 1:

"An unhandled exception of type 'System.Workflow.Runtime.EventDeliveryFailedException' occurred System.Workflow.Runtime.dll"

WF Question #4: Can we get more descriptive exception messages?

Luckily, this error only happens when you're running under the debugger; it's swallowed completely when your program runs normally.

WF Question #5: Can we get exceptions at runtime, too?

Notice also that the OrderEventArgs class is marked with the Serializable attribute. This is required to cross the boundary into the workflow. Without it, you'll get the ever helpful EventDeliveryFailedException exception.

WF Question #6: What boundary are we crossing when fire an event into a workflow?

Further, all objects sent into a workflow need to be serializable as well, like the Order class (also yielding EventDeliveryFailedException if you forget):

[Serializable]
public class Order {
  Guid _orderId = Guid.NewGuid();
  public Guid OrderId { get { return _orderId; } }

  string _customer;
  public string Customer {
    get { return _customer; }
    set { _customer = value; }
  }

  string _desc;
  public string Description {
    get { return _desc; }
    set { _desc = value; }
  }

  public Order(string customer, string desc) {
    _customer = customer;
    _desc = desc;
  }
}

Firing the event is as easy as calling our helper function from outside of our workflow to cross the boundary into the workflow:

class OrderServiceImpl : IOrderService {
  ...
  public void ApproveOrder(WorkflowInstance wf, string comment) {
    if (OrderApproved != null) {
      Guid wfId = wf.InstanceId;
      OrderApproved(null, new OrderEventArgs(wfId, _workflowOrderMap[wfId], comment));
    }
  }

  public event EventHandler<OrderEventArgs> OrderApproved;
}

Notice that we need the workflow instance ID, which we pass in via the WorkflowInstance we get when starting the workflow:

static void Main() {
  WorkflowRuntime workflowRuntime = new WorkflowRuntime();

  // Add IOrderService implementation
  OrderServiceImpl orderService = new OrderServiceImpl();
  workflowRuntime.AddService(orderService);

  workflowRuntime.StartRuntime();
  workflowRuntime.WorkflowCompleted += OnWorkflowCompleted;

  Type type = typeof(EventSinkAndMethodInvoke2.Workflow2);
  WorkflowInstance wf = workflowRuntime.StartWorkflow(type);

  // Simulate human decision time and approve the order
  System.Threading.Thread.Sleep(1000);
  orderService.ApproveOrder(wf, "this is a *fine* order!");

waitHandle.WaitOne();
  workflowRuntime.StopRuntime();
}

Once the event data is fired into the event, the event sink's parameter binding provide enough infrastructure to be able to access the data in subsequent activities, e.g. the code activity that comes right after the event activity:

<Code ExecuteCode="code1_ExecuteCode" ID="code1" />

In the code1_ExecuteCode method, my code can reach over to the event sink activity's parameter bindings and access the data that was fired into it:

public partial class Workflow2 : SequentialWorkflow {
  void code1_ExecuteCode(object sender, EventArgs e) {
    Console.WriteLine("Order: approved w/ comment= {0}",
eventSinkActivity1.ParameterBindings["Comment"].Value);
}

There are three reasons I really don't like this code. The first is that I have to cast to get something typed out of the Value property and I don't like casting. The second reason is that this technique only works through code; I can't bind to a parameter from an event sink to a property on another activity, e.g. the Error property on a Termination activity. The third, and most damning reason, is because this induces coupling from my code activity to my event sink activity. I don't want this coupling to be captured in code, which is another reason to really like the declarative data binding solution.

WF Question #7: Why can't I bind event sink parameters as input into other activities?

Unfortunately, while I can't solve reasons #2 or #3 very well, I can solve them partially and I can solve #1 nicely by adding some fields to my workflow class:

public partial class Workflow2 : SequentialWorkflow {
  Order _approvedOrder;
  string _approvalComment;

void code1_ExecuteCode(object sender, EventArgs e) {
    Console.WriteLine("Order: approved w/ comment= {0}", _approvalComment);
  }
}

The _approvedOrder and _approvalComment fields can be bound to the event sink parameters like so:

<EventSinkActivity
ID="eventSinkActivity1"
  EventName="OrderApproved"
  InterfaceType="EventSinkAndMethodInvoke2.IOrderService">
  <EventSinkActivity.ParameterBindings>
    <wcm:ParameterBinding ParameterName="Comment" xmlns:wcm="ComponentModel">
      <wcm:ParameterBinding.Value>
        <wcm:ActivityBind Path="_approvalComment" ID="{/Workflow}" />
      </wcm:ParameterBinding.Value>
    </wcm:ParameterBinding>
    <wcm:ParameterBinding ParameterName="Order" xmlns:wcm="ComponentModel">
      <wcm:ParameterBinding.Value>
        <wcm:ActivityBind Path="_approvedOrder" ID="{/Workflow}" />
      </wcm:ParameterBinding.Value>
    </wcm:ParameterBinding>
  </EventSinkActivity.ParameterBindings>
</EventSinkActivity>

Now, when the event sink activity fires, these two workflow fields are populated so that by the time the code activity is executed, they're ready for use, all typed up and ready to go. However, while this reduces coupling to some degree, i.e. the code activity just hopes that somebody provides the data and not that is has to be a specific activity, this is not the same as failing to execute an activity until you have valid values to pass to a function. Instead, it's like setting global variables, hoping they're set properly when the code that accesses them needs them and having no idea what's affected if you have to remove them.

Still, with the parameter binding in place between the event sink parameters and the workplace class fields, I can bind the input to an activity:

<Terminate
Error="*d2p1:ActivityBind(ID={/Workflow};Path=_approvalComment)" ID="terminate1"
  xmlns:d2p1="ComponentModel" />

Clearly, this is binding syntax only a mother could love, but luckily the designer did it for me, it's going to change in the next beta and it does most of what I want, i.e. bind the input of the Error property on the Terminate activity to something produced by another activity. It's not the same as binding the event sink activity parameters directly, thereby allowing me to shed this dirty global variable feeling, but it's oh so close.

The VS05b2, WF/WinFXb1 sample code is available for your enjoyment.

Props to Dennis Pilarinos and Anandhi Somasekaran from the WF group for help figuring this out.

0 comments




Open Office is a "real" thing

I use my family as a litmus test of when things are "real" or not. None of them have technology backgrounds or training, so if something is real to them, it's real. For example, it was my grandmother that asked whether she need Windows 95 (she didn't have a computer, mind you). I head from my folks about spyware and phish attacks. And now, my sister-in-law asked me if she should be upgrading her Windows ME machine to OpenOffice.

Of course, OpenOffice isn't an OS, but as far as she knows, MS Office is her current OS, so maybe OpenOffice is what she needs.

I believe that it's in her best interest for me to steer her away from unsupported OSS software unless I want to become her tech support liaison (difficult, since I've never used OpenOffice). Also, she's looking for "something that will function with little or no problems," which I can't claim of any software, free or not.

Still, it makes you wonder if these OSS guys could really make a credible play for the desktop. Obviously, they're not going to produce Office 12 or Windows Vista anytime soon, but they're nipping at our heels pretty credibly if they can attract the attention of the secular members of my family. How much longer before OSS becomes "good enough" for most uses and the latest thing you buy from actual software vendors is just fancier? In fact, how many actual software vendors do we really have left anyway?

0 comments




345 older posts       165 newer posts