Adrian Hara

Working through the .NET maze

  Home  |   Contact  |   Syndication    |   Login
  32 Posts | 0 Stories | 54 Comments | 10 Trackbacks

News

Archives

Post Categories

Friday, November 07, 2008 #

I just installed .net 3.5 sp1, vs 2008 sp1 and thus got access to the latest version of the entity framework. Since I hadn't played with it before, I decided to try. My scenario was more than simple, bordering on lame: save some data in one table in a sql compact database.

So I quickly generated the .edmx using the wizard (which is pretty cool btw) and just ran the app. Unfortunately, when the ObjectContext derived class would be instantiated, i got the following FileNotFoundException:

"Could not load file or assembly 'System.Drawing, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' "

This was pretty strange, since I was targeting the 3.5 version of the framework and I didn't see any explicit need for the 1.0 version of System.Drawing.

After some digging, I came across this MSDN forums thread: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3926655&SiteID=1 . Craig Lee's post is particularly enlightening: "There is a note in the above MSDN page that outlines the effect of keeping the default "res://*" value that the designer writes out.  In your case, you should change this so that it uses explicit paths.  I suspect that because you have "res://*", the EF runtime is scanning your DLL and all referenced DLLs for any EF runtime artifacts".

As it turns out, I was actually using a 3rd party component which had a reference to System.Drawing 1.0 and, because of my default generated connectionstring by the entity model wizard (which should just work, right? :)) it looks like the EF was crawling all referenced assemblies to look for its definition files, ending up by not finding the 1.0 version of System.Drawing because...well... I haven't the 1.0 version of the framework installed :)

Fix was easy: change the connectionstring to use the assembly name, instead of the wildcard search, like so "res://SomeAssembly/<whatever else was generated>".

Hope this helps.

Thursday, September 11, 2008 #

...is not straightforward. The problem is that many (all?) of log4net's classes are not serializable. This includes the implementations of the ILog interface, which means that if you plan on using log4net loggers in your SharePoint workflow code like so...

private ILog logger = LogManager.GetLogger(typeof(Foo));

...you're out of luck. As soon as the workflow is dehydrated, you'll get a nice exception stating that type X (probably LogImpl) is not serializable.

 

One solution to this problem is to recreate the logger each time the workflow is rehydrated or, more simply put, lazy-initialize the value of the logger: if it's null, create it, if not, return it. Although this could be done separately wherever you need it in the project (via perhaps a private property of each class), I thought it would be nicer to abstract this away in a little library. I called it SPLog4net (how imaginative!) and you can find it on CodePlex.

 

The library has two classes (ok, three, including the msdn copy/pasted TraceProvider):

1. LoggerWrapper  - this class implements the ILog log4net interface and acts as a thin wrapper over a real log4net logger. Its main purpose is to lazy-initialize the log4net logger if it's not yet initialized. The usage pattern is very similar to the usual log4net usage pattern:

log4net standard -             private ILog logger = LogManager.GetLogger(typeof(Foo));

LoggerWrapper -                private ILog logger = new LoggerWrapper(typeof(Foo));

    The other little thing that the logger wrapper does is it prefixes each log message with a...umm... prefix. The idea is that, being in SharePoint, you might want to log messages to the ULS log (more on that in a minute). But, as you probably know, the ULS log files are not the most user-friendly things around. So, if you use a utility to view them, such as the excellent SPTraceView, you might want to take advantage of its message filtering feature and have it only display messages starting with a certain.... ahem... prefix :)

2. UlsLogAppender - to be honest I think it's quite strange such a thing doesn't yet exist (I couldn't find it on google anyway ;)), so I wrote it. This class represents a log4net appender that writes to the SharePoint ULS log. You might want to take a look at the source and tweak it, because it has a method which converts the log4net severity levels to SharePoint severity levels and the said method is... well... not totally complete. (which means I only mapped severals level which I thought of use, but you might need more). To use it just make sure you have it in the GAC (or application bin) and added to the log4net config file (or section in the web.config file of your web application).

 

Naturally, you can use the two independently and not only from a workflow.

 

Hope this helps someone and please let me know if I'm either reinventing the wheel or inventing a square one ;)


Wednesday, August 13, 2008 #

Searching for a way to check if a workflow is running on a list item (useful, for example, in an item event handler), I found some code like this:

        public static bool IsWorkflowRunning(SPListItem listItem, Guid workflowId)
        {
            foreach (SPWorkflow workflow in listItem.Workflows)
            {
                if (workflow.ParentAssociation.BaseTemplate.Id == workflowId &&
                    workflow.InternalState == SPWorkflowState.Running)
                {
                    return true;
                }
            }
            return false;
        }

What this code does is iterate the workflows list for the item (point to remember: listItem.Workflows contains not only running workflows, but also cancelled or otherwise) and checks the InternalState for the given workflow id. While it might seem ok, as I found out the hard way, it's not. Correct way to check is like so:

        public static bool IsWorkflowRunning(SPListItem listItem, Guid workflowId)
        {
            foreach (SPWorkflow workflow in listItem.Workflows)
            {
                if (workflow.ParentAssociation.BaseTemplate.Id == workflowId &&
                    (workflow.InternalState & SPWorkflowState.Running) == SPWorkflowState.Running)
                {
                    return true;
                }
            }
            return false;
        }

The somewhat subtle thing to notice is the check: it denotes that the SPWorkflowState is an enum decorated with the [Flags] attribute, which means that the check has to be changed like above.


Friday, August 08, 2008 #

If you're developing workflows for SharePoint 2007, you are probably familiar with the Item property of the SPWorkflowActivationProperties class. Usually your workflow gets an instance of SPWorkflowActivationProperties when the OnWorkflowActivated activity executes, which is bound to a public field on the workflow class so you can hang on to it for the lifetime of the workflow. Perhaps one of the more useful properties of this class is Item, which returns an instance of SPListItem, representing the list item on which the workflow runs.

This is all fine, but maybe for more than two step workflows or for a better design/maintainability of the code, it makes sense (and I try to do it) to encapsulate the properties (access to) and whatever manipulating logic of the list item in a business class, representing the business concept you're dealing with (for example, I find that using classes like Contract or Customer to work with the item's metadata is nicer than working with SPListItem). A small problem with this approach is that you can't simply wrap the SPListItem instance in your business class, because it's not [Serializable], which means that you'll get some nasty error when SharePoint tries to dehydrate your workflow. In light of this, what I do is wrap a reference to the SPWorkflowActivationProperties instance and have a private property called ListItem which just calls the Item property, like so:

        private SPListItem ListItem
        {
              get { return workflowProperties.Item; }
        }

This worked very good for me until a few days ago, when I had a workflow that runs over items in a document library and had to replace the actual document file at some point (like replace a .doc with a generated .pdf from it). The replacement worked fine (I won't go into details, google has answers ;)), but after the replacement, if, during the same call (meaning before the workflow gets dehydrated again), I wanted to do something else with the file, or even call Update() on the SPListItem, exceptions got thrown, with messages like "the file has been modified by blah blah". After some investigations I realized that that although I was modifying the SPFile (which i got from SPListItem.File) and calling Update() on it, this didn't do anything (for example, looking at the SPListItem.File.Name, I'd still get "somepath/somename.doc" instead of "somepath/somename.pdf"). Also, the next logical solution, calling Update() or SystemUpdate() on the SPListItem itself would throw, as stated above. However, if, after modifying and/or replacing the file, I wouldn't do anything else and just let the workflow dehydrate, whenever it would get hydrated again, everything would work: this makes sense, since the SPListItem is not serialized and would be created again after rehydration.

So, the next step would be looking when is the SPListItem of SPWorkflowActivationProperties created and how can we refresh it "on the fly". Reflector shows the following:

ItemProperty

As you can see, the item is actually cached after the first call. This means that when updating the SPFile associated with the item and calling Update() on it, since this doesn't cause the item itself to update (or at least update its SPFile), we have to somehow refresh the whole SPListItem. While the SPWorkflowActivationProperties class doesn't expose some method to do this, it's easy to do with reflection: just set the m_item to null and on the next call to the property, it will be recreated. One thing to note here is that, as seen in the picture above, there are two cases where the item can be refreshed:

  • for non super user workflows, a call is made to the list directly for the item
  • for super user workflow, a call is made to SPWorkflow.ParentItem

Looking at the SPWorkflow.ParentItem with reflector, it turns out that the value backing the property is also cached:

ParentItemProperty

So, in order to be fully covered for all situations, the method to invalidate the list item and make sure it's refreshed should look like so:

        private void InvalidateWorkflowPropertiesItem()
        {
            FieldInfo parentItemField = workflowProperties.Workflow.GetType().GetField("m_createdParentItem", BindingFlags.NonPublic | BindingFlags.Instance);
            parentItemField.SetValue(workflowProperties.Workflow, null);

            FieldInfo itemField = workflowProperties.GetType().GetField("m_item", BindingFlags.NonPublic | BindingFlags.Instance);
            itemField.SetValue(workflowProperties, null);
        }

 

Normally, you should just call this method after doing something that causes the SPFile for an item to change, but I guess there are probably other situations where refreshing of the list item would come in handy.

ps: another approach to the basic problem that the SPListItem isn't serializable would be to keep it's unique id and retrieve it like that every time you need it, but this could mean implementing the caching and getting mechanisms yourself if you want to have some business class wrapper over it (meaning that you can't just GetByUniqueId() it every time you need to change a property, because you'd end up with a different instance every time and would have to be careful about updating it)


Wednesday, July 16, 2008 #

The following gotcha was a pretty nasty one for me (I'd say about 2 hours of lost time worth). Supposing you have some content types deployed at the site collection level and you want to add them to a document library of a sub-site (web) plus configure them a little bit, like so:

            foreach (string contentTypeName in ToAttachToListContentTypeNames)
            {
                // Get site collection content type
                SPContentType siteContentType = web.AvailableContentTypes[contentTypeName];

                // Add to library and configure
                SPContentType libraryContentType = library.ContentTypes.Add(siteContentType);
                libraryContentType.DocumentTemplate = string.Empty;
                libraryContentType.Update(false);

               // Maybe some more work, like adding some linked fields here?...
            }

This turned out to result in some weird behavior: supposing I had 3 content types to add to the document library, the first one of them (which would also be the first one added) always got some of its properties wrong. For example, even though the code sets the DocumentTemplate explicitly to string.Empty, the resulting document library content type (the first one added) would always get the "template.doc" template instead. Weird stuff happened also to some linked fields I was adding, like the first content type would always get one linked field less than the others

Now, unfortunately (or fortunately?) I didn't have the time to Reflect over the sharepoint code to see why this happens, but after some trial-and-errors I found that it seems the call to "library.ContentTypes" somehow merges the "web.AvailableContentTypes" with the library's content types. Since this call is done in the foreach loop, it means that, for each subsequent loop step, some settings for the content types added in prior steps are "merged" (read overwritten) with the ones of the site collection content types. What's weird is that:
1. It only seemed to happen for the first added content type (so the first step in the loop). The other two were fine.
2. It only "merged" some of its properties, like DocumentTemplate and *some* field links, not all

Anyway, the fix is to take the call to "library.ContentTypes" out of the loop and assign to a variable. Then you can just iterate over that "local" collection and do stuff:

            SPContentTypeCollection availableContentTypes = web.AvailableContentTypes;
            foreach (string contentTypeName in ToAttachContentTypeNames)
            {
                // Get site collection content type
                SPContentType siteContentType = availableContentTypes[contentTypeName];

                // Add to library and configure
                SPContentType libraryContentType = library.ContentTypes.Add(siteContentType);
                libraryContentType.DocumentTemplate = string.Empty;
                libraryContentType.Update(false);
            }
            library.Update();

            // Add some fieldlinks to the library content types
            SPContentTypeCollection libraryContentTypes = library.ContentTypes;
            foreach (string contentTypeName in ToExtendWithFieldLinksContentTypes)
            {
                SPContentType libraryContentType = libraryContentTypes[contentTypeName];

                SPFieldLink someFieldLink = new SPFieldLink(web.Fields["some field"]);
                libraryContentType.FieldLinks.Add(someFieldLink );

                // Maybe add some more...
              
                libraryContentType.Update(false);
            }

This seems to work, but granted, is strange enough to do...

Sunday, July 13, 2008 #

To paraphrase a heavyweight blogger, this is the first post in (probably) a infinite number of posts about Sharepoint Gotchas.

Today's gotcha: why doesn't my custom content type have any columns?

It took me quite some time (including a little foaming-at-the-mouth time) to figure this out. Suppose you declare a custom content type in CAML that you'd like to just inherit all its columns from its base content type, without defining any of its own, like so:

<ContentType
      ID="0x010100D4C041607B6A400dB211CC424B2D153BAB"
      Name="My Content Type"
      Description="Foobar content type"
      Version="0"
      Group="Foobar Group" >
  </ContentType>

All is well, it deploys fine, just that when you want to use it it turns out it didn't actually inherit any of its parent type's columns, but rather has an empty Columns list. The problem is pretty subtle, if you ask me, and stems from the fact that you should've declared the content type like so:

<ContentType
      ID="0x010100D4C041607B6A400dB211CC424B2D153BAB"
      Name="My Content Type"
      Description="Foobar content type"
      Version="0"
      Group="Foobar Group" >
    <FieldRefs/>
  </ContentType>

Did you spot the difference yet? It's the <FieldRefs/> element. So even if you don't declare any columns, you still have to include the empty FieldRefs element. What's even nastier is that the schema validates without it :(...

Tuesday, July 08, 2008 #

Although this might be present in other versions of DNN too, I've found that if I add the attribute "theme=something" to the <pages> element in web.config, I get a compilation error in FCKImageGallery.aspx:

"Error    129    Error parsing attribute 'theme': The 'theme' property is read-only and cannot be set.    "

That is because the page's codebehind class derives from the FCKGallery class, which already has a Theme property. This is unfortunate, because, although DNN has its own skinning mechanisms, you might also want to use the standard asp.net themes (like for example for CSS Friendly Control Adapters).

The only workaround I've found so far for this is to add a web.config file to the Fck folder that looks like so:

<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
      <pages theme=""> </pages>
    </system.web>
</configuration>

This "local" config file basically overwrites the application web.config file and tells the compiler that themes are not needed for the local Fck folder.

As I'm new to DNN, maybe I'm missing something, so if you know anything about this, please let me know ;)

Sunday, July 06, 2008 #

Due to much frustration and lost time, I'll try to summarize in this post the "naming" conventions to be used when defining and/or referencing IDs (some of which are, in fact, GUIDs, and others contain GUIDs) in Sharepoint 2007.

If you're thinking "what the heck?! aren't GUIDs always the same format?!" you're half-right: they should be, but not in sharepoint. So if you're a newbie to sharepoint 2007 (or wss 3.0) like me, the following tips regarding how element id's should be written might save you some time (because if you get them wrong, the error messages from sharepoint are somewhat... cryptic). I've two conventions pinned down so far, but I'm just learning, so if you know any more, please share.

  1. Site Column definitions: defined using a <Field> xml element (more info on MSDN), their id's must have the form {GUID}. Note the brackets, they are mandatory, else you'll get an error (in the logs only) along the lines of "unable to locate the xml-field definition for FieldName with FieldId 'your_guid'". Another thing to remember is that the brackets are also mandatory wherever this site column is referenced (like in a content type). Thanks to Edwin Vriethoff for this tip.
  2. Content Types: as you probably know, the content type id is not actually a GUID, but a concatenation of the base content type id, "00" and a Guid. The catch here is that the Guid (third part in the concatenated string) must be written without dashes. So, instead of "0x010012B0E62B-D58E-4d6e-94A1-E36B3F7807E1", write "0x010012B0E62BD58E4d6e94A1E36B3F7807E1". Easy one to get wrong, especially if you use vs.net's guid generator in registry format. The error message from sharepoint in this case is very clear and helpful: "value does not fall within expected range" :)
It's actually too bad that 1) these gotchas exist and 2) the problems are not easy to figure out using the error messages from sharepoint. Hopefully, this post will help somewhat.

Wednesday, April 16, 2008 #

In this post I’ll try to talk a little bit about how you can get msmq work and sql 2005 work to go together in a transactional context. I think it’s fair to say none of the information is new, but I did a bit of digging around the net to find it, so maybe having it all in one place would be of help. Any thoughts or suggestions are more than welcome and I’d be happy to update the post for posterity J So…

…what do you do if you need to:

a)      Receive a message from a MessageQueue

b)      Based on that message, save something to a Sql table

c)       Send a message to (another) MessageQueue

d)      Have all of the above work or fail together (like in…uhm…a transaction ;))

As you might have guessed by now, you need to somehow “wrap” all of the above operations in a transaction, or, let’s say, in the same transactional context. In .net 2.0+, this is deceptively simple:

                  using (TransactionScope transaction = new TransactionScope())

      {

            // do some work

            transaction.Complete();

      }

 

So all you have to do is make sure that all of the operations you need to do (like a,b and c above) are inside the TransactionScope and the work will be transactional. 

 

Now some things to be aware of:

1.       The above will work with MSMQ out of the box, but only with some ado.net database providers, namely those whose implementation looks to see if there is an ambient transaction and enlist in it. The Sql and Oracle providers, at least, support this, so they will work ok using this approach.

2.       You need to have the DTC (Distributed Transaction Coordinator) enabled and configured correctly. I won’t go into details about how to do this, as there is LOTS of information about it on the net. You need it because you have two different transaction types (one for MSMQ and one for the database), whose managers need to work together in order to achieve the “transactional context” goal. They do this by enlisting the help of a “higher power”, namely the DTS.

3.       For your message queues to support transactions, they need to be marked as “Transactional” at creation time. Be careful about this one: I found no obvious way (from the MMC console) to change this after the queue was created AND the behavior of the queue I observed was silent (for example, when sending a message to a transactional queue without using a transaction, the message is not sent, but you don’t get an exception either, so it fails silently).

4.       There’s a class called MessageQueueTransaction. The usage pattern of this class is similar to an Ado transaction, having Begin() and Commit() methods and also requiring that when you do MessageQueue operations, you use overloaded methods which take the transaction as a parameter, like MessageQueue.Send(Message msg, MessageQueueTransaction transaction). BUT, when using a transactional MessageQueue within a TransactionScope, you don’t need this class. What you do is create a MessageQueue normally (like in a transaction-less context) and use it. The only thing you need to do is tell the queue to use transactions. You do this using overloads of Send() and Receive(), like MessageQueu.Send(Message msg, MessageQueueTransactionType transactionType):

            using (TransactionScope transaction = new TransactionScope())

            {

                  Message inputMsg = inputQueue.Receive(MessageQueueTransactionType.Automatic);

                  // do some work

                  transaction.Complete();

            }

5.       Beware of long-running transactions. For example, if your use case is such that you need to receive a message from a queue first, then write something to the DB, you might be tempted to do the following:

 

using (TransactionScope transaction = new TransactionScope())

{

      using (MessageQueue someQueue = new MessageQueue("<queue connection>"))

      {

            Message msg = someQueue.Receive();

            // do something else

      }

      transaction.Complete();

          }

 

This is not ok! It’s wrong because the Receive() method is synchronous and if there’s nothing in the message queue, it will block until there is. This means that your transaction will remain open for that amount of time and will probably time-out, resulting in a MessageQueueException with a message along the lines of “The transaction operations sequence is invalid”.

The correct way to do this is using the BeginPeek() (not BeginReceive(), since this is not designed to work for transactional queues) method, subscribe to the PeekCompleted event of the queue and, in the event handler, use a transaction to actually Receive() any messages from the queue, like so:     

            inputQueue = new MessageQueue(("<queue connection>");

            inputQueue.PeekCompleted += inputQueue_PeekCompleted;

            inputQueue.BeginPeek();

 

            private void inputQueue_PeekCompleted(object sender, PeekCompletedEventArgs e)

            {

                  using (TransactionScope transaction = new TransactionScope())

                  {

                        Message inputMsg = inputQueue.Receive(MessageQueueTransactionType.Automatic);

                        // do some work

                        transaction.Complete();

                  }

                  inputQueue.BeginPeek();

            }

6.       If you only need to do transactional work related to message queues, without any other resources that need to be in the transactional context (like databases), don’t use the TransactionScope, but rather the MessageQueueTransaction class I mentioned earlier. Using this class to control transactions related only to message queues comes with a lesser performance penalty than using a full distributed transaction controlled by DTC.

 

Links and resources:

Using the Microsoft MessageQueue

MSMQ Transactions on CodeProject

MSDN Help on BeginReceive()

Nine Tips to Enterprise –proof MSMQ

 

 


Friday, March 28, 2008 #

Disclamer: although I'm pretty convinced there's a better way to do this, I haven't found it, so here goes...

Suppose I have this interface:

public interface IFoo

{
  int Add(int a, int b);
}

Now I’d like to mock it (using NMock2), like so:

IFoo mock = new Mockery().NewMock<IFoo>();

Expect.Once.On(mock).Method("Add").With(5, Is.Anything).Will(Return.Value(10));

Console.WriteLine(mock.Add(5, 6));

Leaving aside why I’d want it to return 10 regardless of the value I add to 5 J, the above doesn’t work. More specifically, calling mock.Add(5, 6) throws.

The reason is that the call to With will resolve to the actual “With(Object[])” method, which will internally build an array of EqualMatcher objects, set their expected value to the expected parameter values (5 and Is.Anything) and call “Matches(object actualParameter)” on them, which finally calls Equals(expectedParameterValue, actualParameterValue) . Looking at the call above, 5 will obviously be equal to 5, but Is.Anything is actually an instance of the AlwaysMatcher class, and Equals(6, alwaysMatcherInstance) is obviously false.

The trick to fix this is to use the other With() overload: With(Matcher[]), like so:

Expect.Once.On(mock).Method("Add").With(new EqualMatcher(5), new AlwaysMatcher(true, "bla")).Will(Return.Value(10));

Looks crappy and is crappy, but it works, because this will mean that each of the two matcher’s specified above Matches() methods will be called (and AlwaysMatcher just returns the value given in its constructor, in our case “true”). Basically before we had two EqualMatchers, now we have one EqualMatcher and an AlwaysMatcher.

With this in mind, the next line is almost the same and also works:

Expect.Once.On(mock).Method("Add").With(new EqualMatcher(5), Is.Anything).Will(Return.Value(10));

Update, the following also works (thanks to Nigel Thorne):

Expect.Once.On(mock).Method("Add").With(Is.EqualTo(5), Is.Anything).Will(Return.Value(10));

Hope this helps someone :)


Update 12 may 2008a similar scenario  can be encountered when mocking a call to a method which has a params parameter like, for example,  DoFoo(params object[] someParams). The problem here is that when defining the parameter values for this method, the  NMock method With(object[] params)  will (as explained above) loop through the object array and create a matcher for each one, but the actual call to the mocked (DoFoo) method will be with only one parameter (the object[] someParams). So, at runtime, NMock will see that you called a DoFoo(object[]) with one parameter, but defined an expectation of N parameters (the N matchers he created) and will fail.

To fix this, when defining the expectation, you need to enclose the parameter values in one single matcher, so instead of

Expect.Once.On(someMock).Method("DoFoo").With(paramValue1, paramValue2, ..., paramValueN).Will...

do

Expect.Once.On(someMock).Method("DoFoo").With(Is.EqualTo(new object[] {paramValue1, paramValue2, ..., paramValueN})).Will...

Wednesday, March 26, 2008 #

I've ran into a bit of an issue today that involves MSMQ. I had an Asp.Net webservice who also ran a job (basically a new Thread that loops forever) that was doing a Receive on a MessageQueue object and writing the Message it got (or rather the body of the message) to a database. The business of the job was such that calling Receive, which is synchronous (blocking), was ok.

It all worked fine, except that whenever I changed something in the web.config file and the AppDomain would get recycled, the next message posted to the message queue would get read from it, but it would never get written to the database. Since no one else was reading from the queue I thought it was an issue with my code. So I did some logging and debugging and in the end came to the conclusion that after the AppDomain recycled, the first message posted to the queue would get read by somebody, but not the code in my app. The second (next) message posted to the queue, plus all the next other messages posted from then on would get read fine and written to the database. Huh?!

Unfortunately, since it's my first experience with MSMQ, I don't have a definitive answer for the problem. Maybe some of you could help. My theory is that somehow... somehow... since the Receive call is synchronous, when the AppDomain recycles, there's still something left somewhere (outside of the app or even the .NET framework, I'm thinking about an OS thing) that is waiting to get the first message to be posted to the queue. So when that message gets posted, the "something" gets it, instead of my app. As I said, it's just a theory, so if you know anything about this, please share.

Anyway, I did find a workaround: it involves using the BeginReceive() method and subscribing to the ReceiveComplete event of the MessageQueue. This approach worked, resulting in the event handler for the ReceiveComplete event being called even for the first message posted to the queue just after a AppDomain recycle. The only thing needed then is to just BeginReceive() again on the queue, after getting the message, to subscribe for the next one.

Sunday, September 16, 2007 #

When you want to pass data from a Sharepoint workflow to a task InfoPath form, the recommended method (in fact I don't know of any other one) is using a "receive data" data source, defined by the ItemMetadata.xml file. The structure of this file is documented in various places around the net, for simple data types (like, say TextBox-es or CheckBox-es). Unfortunately, when it comes to passing data to complex controls like the drop-down list , it seems there just is a void of information, although people are in need of such a functionality. Several posts on the MSDN forums debate the issue, like this one or this one, but, although they pointed me in the right direction, they failed to give a complete and conclusive example of how to do it. So after going through the pain of getting the stuff to work, here's a step by step walkthrough that should, hopefully, spare you the "research" involved:

The overview, before going into details, is this: create some fields in the main data source to which the drop-down list will be bound, then populate these fields dinamically at runtime with data from the secondary (ItemMetadata) data source.

You might be surprised to hear that the drop-down list will read its data from the main data source (I know I was), since the first idea would be to have it get its data from the "receive data" data source (defined by the ItemMetadata.xml file). Unfortunately it sems that the ItemMetadata.xml file can't contain "complex data" (here's that word again...), just simple "rowset" data, so it's no good for populating a drop-down list (whose fields are defined by two attributes, Name and Value). So what we have to do is basically receive the data for the list in a (ONE) field of the secondary data source, then "copy" this data into the fields of the main data source.  I say "copy" because since we're putting "complex" (three times now...) data into one field, there might be multiple ways to do it. I took the approach where the field will store the exact xml needed by the main data source fields, and then just replace it at form load. So without further ado, here's what you have to...umm...do:

  1. In the InfoPath designer, goto Data Sources and:
    • add a group, let's call it "Teachers"
    • in this group add a repeating field, called "Teacher"
    • under Teacher add two fields, say "Name" and "DomainAccount" (these will be the display text and value of the drop-down list entries)
  2. In your drop-down list properties select "look up values in the form's data source" and:
    • for "entries" choose the Teacher field
    • for "value" choose "DomainAccount"
    • for "display name" choose "Name"
  3. Open your ItemMetadata.xml file in some editor (Notepad?) and add a new field, for example ows_TeachersData. Be sure to update your secondary data source with the new schema, by specifying the .xml file again.
  4. At this point the drop-down list is set up to get its data, and its data will come in the ows_TeachersData field in the secondary data source. What we still need to do is move this data from the ows_TeachersData field to the Teachers group fields in the main data source. For this you need to have VSTA installed (you can install it from your MOSS dvd). In the form menu goto Tools -> Programming -> Loading event. This will launch VSTA and create an event handler for the form's loading event.
  5. In the FormEvents_Loading method, write the following code:
public void FormEvents_Loading(object sender, LoadingEventArgs e)
               {
                   if (this.DataSources == null)
                  {
                      return;
                  }
 
                 XPathNavigator xmlSource = this.DataSources["ItemMetadata"].CreateNavigator();
           
                XPathNavigator teachers = this.MainDataSource.CreateNavigator().SelectSingleNode("/my:TeachersForm/my:Teachers", NamespaceManager);
                teachers.InnerXml = xmlSource.SelectSingleNode("/z:row/@ows_TeachersData", NamespaceManager).InnerXml;
             }
 

  All that's left to do at this point is, in your workflow code, to build and populate the TeachersData field. In won't go into details as it's explained elsewhere, but the data itself should look something like: "<my:Teacher my:Name="blah" my:DomainAccount="blablah"></my:Teacher><my:Teacher my:Name="blah1" my:DomainAccount="blablah1"></my:Teacher>...etc"

As a final tip, if you first want to test your code before deploying it (which you should do, as testing it directly in Sharepoint is a pain, because of the needed iisresets and such), you can just press F5 in VSTA to debug. An essential condition here is that the form security is set to "Full Trust", as opposed to "Domain" which should be set when deploying to Sharepoint, otherwise you'll get an error when opening the form.


Saturday, September 15, 2007 #

After googling it and not getting lucky, I quickly put together a Do-While activity for Windows Workflow Foundation. The thing is basically a copy/pasted (from Reflected code) version of the BAL WhileActivity, with a small change to evaluate the condition at the end (doh!) and some small changes to the designer. I haven't tested it extensively, but at first glance it seems to work. Any feedback is greatly appreciated.

The sad thing is that the WhileActivity in the BAL is sealed, so there's no chance of tweaking it to become a do-while. Also sad is that its designer, designer theme and validator are also sealed, so...umm...I had to copy/paste those too (with small changes of course ). Another annoyance is that I haven't found an obvious way of making the default glyph inside a CompositeActivity go away (or at least move to somewhere else), and a summary look over its designer revealed that it might not be even possible to do. So I had to have the activity display two glyphs in the designer, to distinguish itself from the WhileActivity.

I've created a project on CodePlex (www.netfx3.com seems to have some problems with accepting submissions) and since it's my first time there, please be gentle . I've tried uploading the source code into the "Source Code" section, but ran into the lame-o command line "cpc" tool, with which I couldn't do a "addfile *.*", so I gave up (I'm not saying that it can't be done, but if that didn't work...). So I created a release which includes the source code itself, which means you'll have to build it before using the activity.

In the end, I hope I haven't duplicated some code that already exists somewhere out there... You can get the activity at: http://www.codeplex.com/wfdowhileactivity


Saturday, August 04, 2007 #

I've recently started learning Workflow Foundation in conjunction with Sharepoint Server 2007. One particularly annoying issue I have with it is the development setup needed: its kinda designed to only work on Windows Server 2003. What I mean by this is that some sdks don't install at all on other OSes (like the OfficeServerSDK or the Windows SharePoint Services 3.0 Tools: Visual Studio Extensions), while others do install, they even install the Sharepoint workflow project templates in Visual Studio, but they don't deploy any of the needed .dlls you have to reference in those very same project templates (Windows Sharepoint Server SDK).

 

So what's a developer (that's not running Windows Server 2003) to do? Check out the following article for instructions on how to set up your regular, non WS2003, machine so that you can develop workflows for SPS on it: http://zgud.spaces.live.com/blog/cns!76596824C4119...

 

Ok, so after reading the above mentioned article and following its instructions you're all set and can develop workflows "locally". Then you deploy them to the "remote" Sharepoint machine and run them. Here basically one of two things happens: a) they run. b) they don't :) 

 

In case of b), and presuming that everything is deployed correctly, but there is an error in the actual workflow code, you have several options:

  • check out the Sharepoint log files, located at "%Program Files%\Common Files\Microsoft Shared\web server extensions\12\LOGS". You can usually start from the last file backwards and look for words like "workflow".
  • instrument the workflow so that it records some nice messages to the history list. While this is fine, you can't get too verbose with these messages and you definitely can't get too specialized. Still, if you really really want to go this way I suppose you can do some try/catch -es and log the exceptions to the history list, strictly during development. Anyway, this is a poor man's substitute for real debugging.
  • actually debug the running workflow

 

The last option, of course, should only be used against a testing server, not real production environments, but is definitely the most powerful of the three. So how do you do it? Well, after googling around and finding nothing (although, to be honest I found this blog entry which, somewhere in the comments, states that remote debugging against Sharepoint workflows isn't supported; which is not true :)) I dug a little bit and came up with the following lists:

Prerequisites:

  1. Visual Studio .Net 2005 installed on the remote machine (as documented here)
  2. Workflow Foundation extensions for VS2005 installed on the remote machine
  3. Windows Sharepoint Server SDK installed on the remote machine
  4. Remote debugging monitor running on the remote machine

 

Steps:

  1. Build your workflow solution and copy the needed deployment files to the remote machine
  2. Deploy the files in the needed folders and gac. Make sure to include in the gac ALSO any additional assemblies (like custom activities) PLUS ALL the .pdb files you need (the install.bat file which you get for Sharepoint project templates deploys by default only the main assembly)
  3. Run iisreset
  4. From the local machine, VS2005 -> Debug -> Attach To Process
  5. Make sure you select "Attach to Workflow Code"
  6. Choose the remote machine's name (or IP address), and the appropriate w3wp process.
  7. Run the workflow on the SPS remote machine and watch your breakpoints get hit.

 

In conclusion I must say that although you can do the stuff above, I also realize that given the nature of the prerequisites needed on the remote machine, you might as well just copy your project over there and debug there locally. This is, of course, ok, as long as you don't run into any other annoyance factors, like no source control on the remote machine, remote user connections limit etc.


Tuesday, June 05, 2007 #

A colleague of mine ran into this today and we scratched our heads for some time, before he figured out what was wrong. Anyway, I turned some production code into a kind of an extreme example, but what do you think of the following screenshot:

CodeExample

 

Console.WriteLine fortunately prints "Base", but what about that debugger tooltip which shows the value of the private property? [O_o]