Monday 26 February 2007

Getting Custom Actions to work in SharePoint Designer

I'm writing custom actions (i.e. Activities) for SharePoint Designer at the moment. It's lovely when it works, and incredibly frustrating when it doesn't. The problem is not so much that it's particularly hard once you've got the basics sorted, it's that it's so poorly documented, and so much of it is literally driving blind.


For instance, I've just spent another typical afternoon's debugging session trying to work out the following error in SPD.


The list of workflow actions on the server references an assembly that does not exist. Some actions will not be available. The assembly strong name is ... Contact your server administrator for more information.”


And I assure you, the assembly *does* exist. This is all made even more frustrating by the fact that I'm working against a development install of SharePoint remotely, and TDD just isn't an option here yet. I mean it's awkward enough for local ASP.NET pages, let alone ones that have to run remotely. Then factor in Windows Workflow, which is a horror to run in nUnit. Well, K. Scott Allen has been trying, but it's far from ideal.



So The last few hours have been characterised by making a small change, deploying and GACing assemblies, possibly, recopying .actions files, iisresetting, restarting SPD, screaming, then doing the same thing again. Over. And over. Again.



Basically I'd added a new Activity to my assembly, but it was an extremely simple one, so I'd had the confidence to write it all out by hand. Then I started getting this error, seemingly out of the blue. I looked over and over the code, compared it with a working Activity (One that was still working on a deployed workflow despite SPD apparently thinking it didn't exist.), compared it with the existing SPD activities via Reflector.. naughty, I know ;oP



I finally came upon this post today from Jason Nadrowski, which gave an insight into the problem. God knows how he found all this out, but I'm ever so glad he did.



http://blogs.informationhub.com/jnadrowski/archive/2007/01/12/8128.aspx



Thank you Jason, I tried to post a comment to your blog to say thanks, but it wouldn't let me. So I'm blogging it instead.



Jason's basically pointed out that When SPD loads your custom actions file, it also asks the server to test your objects. If any fail, SPD reports this error. And the server doesn't log the detail or the truth of the fact anywhere. So I don't know who this error message is aimed at. It's too technical for typical end users, it asks them to ask their administrator for more info, except he won't be able to give it, and it's a blatant lie if targetted at developers.



So anyway, armed with this new knowledge, I dug around. And I found my problem. Purely by speculation too. This may be obvious to the few of you who have been playing with XAML since the first Avalon CTP, if it is indeed a DependencyObject constraint, but it was sure news to me.



DependencyProperties need to be named [public property name]Property or they fail to work in SharePoint. so,


public static DependencyProperty EmailProperty =
  DependencyProperty.Register(
    "Email",
    typeof(string),
    typeof(MyActivity)
  );

public string Email {
  get {
    return base.GetValue(MyActivity.EmailProperty) as string;
  }
  ...
}


will work fine, but, freakishly, and not a little infuriatingly,


public static DependencyProperty _depPropEmail =
  DependencyProperty.Register(
    "Email",
    typeof(string),
    typeof(MyActivity)
  );

public string Email {
  get {
    return base.GetValue(MyActivity._depPropEmail) as string;
  }
...
}


will fail with the "assembly does not exist" error. And this is RTM! I do hope they patch it.

Tuesday 20 February 2007

Writing office documents over HTTPS

I love computers. Every job you do, every bit of code you write, no matter how simple or complex, you learn something new somewhere along the way.

And today was no exception. I had to deploy an update to our company's extranet. it was a fairly thorny one because the database schema had changed enough to make it an issue, well, the whole application had changed enough to make it an issue. Anyway, one of these new features was customer reporting. Nothing too exciting, you pick a date range, runa a SQL query and spit out the results. But we also provide a nice pretty formatted export to Excel using an in-house developed SpreadsheetML library, and write it out to the response.

The usual stuff, I'm sure we've all been there:


response.ContentType = "application/vnd.ms-excel";
response.AddHeader("Content-Disposition", "attachment; filename=" + filename );


Then, when I deployed it to the live site (at midnight!) it wouldn't work. Strange. It's exactly the same code. It's writing text to the response (XML) and setting a content type. That's it really, so why wouldn't it work?

Luckily, I found This KB article.
Turns out that Office documents don't download over HTTPS because, Quote:

In order for Internet Explorer to open documents in Office (or any out-of-process, ActiveX document server), Internet Explorer must save the file to the local cache directory and ask the associated application to load the file by using IPersistFile::Load. If the file is not stored to disk, this operation fails. When Internet Explorer communicates with a secure Web site through SSL, Internet Explorer enforces any no-cache request. If the header or headers are present, Internet Explorer does not cache the file. Consequently, Office cannot open the file.

Well, blow me down!

So, I added this line before doing anything else, and bingo!


response.ClearHeaders();


I mean, it figures and all, but I'd really rather not have found this out at half past 12 in the morning, instead of trying to get some sleep! I bet I'm the last person in the web world to find this out too. Bah.