Friday, December 29, 2006

IFilter Text Extracter now hosted on CodePlex

My IFilter Text Extractor that I mentioned previously has now been uploaded to it's new CodePlex home. If you have any enhancement requests or problems, please use the appropriate forums on CodePlex.

In VB.Net, the code to extract up to 64K of text from the file C:\Test.doc is as simple as

Dim te As ExtractTextLib.TextExtractor = New ExtractTextLib.TextExtractor
Dim sText As String
sText = te.ExtractText("C:\Test.doc", 64 * 1024)

ASP.Net RSS Toolkit published on CodePlex.

In February of 2006, Dmitry Robsman released a very cool RSS Toolkit that eases both consuming and publishing RSS feeds in an ASP.Net application.

The RSS toolkit includes support for consuming as well as publishing RSS feeds in ASP.NET applications. Features include:

  • RSS Data Source control to consume feeds in ASP.NET applications
    • Works with ASP.NET data bound controls
    • Implements schema to generate columns at design time
    • Supports auto-generation of columns at runtime (via ICustomTypeDescriptor implementation)
  • Caching of downloaded feeds both in-memory and on-disk (persisted across process restarts)
  • Generation of strongly typed classes for RSS feeds (including strongly typed channel, items, image, handler) based on a RSS URL (the toolkit recognizes RSS and RDF feeds) or a file containing RSS definition. Allows programmatically download (and create) RSS channels using strongly-typed classes. The toolkit includes:
    • Stand-alone command line RSS compiler
    • Build provider for .rssdl file (containing the list of feed URLs)
    • Build provider for .rss file (containing RSS XML)
  • Support for generation of RSS feeds in ASP.NET application including:
    • RSS HTTP handler (strongly typed HTTP handlers are generated automatically by the build providers) to generate the feed.
    • RSS Hyper Link control (that can point to RSS HTTP handler) to create RSS links.
    • Optional secure encoding of user name into query string to allow generation of personalized feeds
  • Set of classes for programmatic consumption and generation of RSS feed in a late-bound way, without using strongly typed generated classes

The toolkit is packaged as an assembly (DLL) that can be either placed in GAC or in ‘bin’ directory of a web application. It is also usable from client (including WinForms) applications.

RSS Toolkit works in Medium Trust (RssToolkit.dll Assembly either in GAC or in ‘bin’) with the following caveats:

  • If the ASP.NET application consumes RSS feeds, the trust level must be configured to allow outbound HTTP requests.
  • To take advantage of disk caching, there must be a directory (configurable via AppSettings["rssTempDir"]) where the trust level policy would allow write access. However, disk caching is optional.

In March, he updated it to release 1.0.0.1 adding a couple much-needed features:

  • Added MaxItems property to RssDataSource to limit the number of items returned.
  • Added automatic generation of <link> tags from RssHyperLink control, to light up the RSS toolbar icon in IE7. For more information please see http://blogs.msdn.com/rssteam/articles/PublishersGuide.aspx
  • Added protected Context property (of type HttpContext) to RssHttpHandlerBase class, to allow access to the HTTP request while generating a feed.
  • Added generation of LoadChannel(string url) method in RssCodeGenerator so that one strongly typed channel class can be used to consume different channels.
  • Fixed problem expanding app relative (~/…) links containing query string when generating RSS feeds.

Then in November, he decided that the project needed to get hosted somewhere and chose CodePlex and solicited someone to shepherd the project. I got the nod, and have re-released his 1.0.0.1 release and posted the source in the new CodePlex project ASP.NET RSS Toolkit. I want to make absolutely clear that up to and including this release, I haven't done anything to what Dmitry released. All the good work is his! In the upcoming couple of weeks, I'll be putting his tools to good use and will be growing the project, if you have any ideas what you want added, please use the project forums out on CodePlex. First on my list of things to support is full ETag and Last-Modified header support to enable bandwidth reduction. I also plan on adding event handlers to deal with redirects for feeds that move to a new home.

Thursday, December 28, 2006

The .Net Framework 2.0 language pack install fails on Vista

I've discovered that you can't install the .Net Framework Language Packs on Vista. They all claim to have already been installed, yet the localized resources directories are not present.

Looks like the language pack checks if the key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\LCID exists (where the LCID is that of the pack). If the key exists, the pack declares that it has already been installed (it hasn't) and thus all the localized resources are not installed.

I have found an ugly workaround (note the values, delete the appropriate key, install the language pack, restore any missing values, set any that are now wrong). Reported, validated and workaround entered on Microsoft's Connect site, feel free to vote here.

Also, the Arabic language pack is built wrong. The internal INI file doesn't even provide a LCID in the key-to-check so it balks outright. I fixed that by extracting the pack manually, fixing the INI and following the above workaround. This issue is reported on Connect here.

The language packs I'm talking about install the localized resources for the various provided languages for all the user-display strings in the .Net Framework. This includes localized versions of exception messages, the messages shown by validators, ASP.Net wizards, etc. You install these to support end-users running in thier own language, especially for ASP.Net applications.

Let the whining commence

Don't get me started about how stupid it is that:

  1. Picking what language pack you want to download changes the language of the page itself.
  2. The file you download is served up as langpack.exe (not named uniquely by the locale).
  3. The language of the installer for the language pack doesn't respect the current language of the person running the installer, so you better watch carefully to figure out where the buttons move to when doing a RTL language or what the error messages that display mean if you aren't a native speaker.
  4. There isn't a single file that you can use to install all the language packs.

Can you imagine being a vertical-market developer that needs to show his customers how do this hackery?

Sunday, December 17, 2006

Extending the available range of DateTime in SQL Server 2005

Since I get so many hits for my various SQL DateTime articles, I thought I would point out an awesome contribution by Mladen Prajdic on building a UDT for SQL Server 2005 in CLR that gives you all that standard SQL Server DateTime behavior over the full range of the .Net DateTime class (01-01-0001 - 12-31-9999).

Nice work, Mladen!

Tuesday, December 12, 2006

COM and IDL and C++ and IFilter, oh my!

It's a blast from the past! On the Developmentor Advanced .Net mailing list recently, there was a bit of discussion about doing text extraction from Word documents. This is something I did in a past life and let me tell you it wasn't any fun.

First we tried using WinWord as an automation server. We would open the file, then save as text. This leaked resources horribly and was slow. It eventually would just lockup and the DCOM would go to sleep... not good for a product (at that time) based on a MTS/COM+ server running RDO components under IIS 5.

Next we tried about twenty or thirty "conversion" programs that were supposed to allow batch or individual conversion of file. This was even worse than using WinWord as the fidelity of the conversion was horrible at best.

Then we tried a couple of commercial libraries for data conversion. They were better at getting the text, but honestly less stable than WinWord.

Then it hit me, in the shower as all good ideas do, doesn't Microsoft's Index Server do a ripping good job sucking out the text of documents to index them? Obviously that can't be unstable or Index Server's crawller would die all the time. So I delved a bit into how Index Server works. Turns out that it loads a COM component and uses the IFilter interface to ask a pluggable filter to extract the text. All I had to do was find an example of an IFilter consumer. Trouble was, there weren't any... sure you could find a ton of IFilter providers to install and enable Index Server to crawl the files, but good luck finding an example of calling one.

Eventually I worked it all out and wrote a COM component of my own in C++ that loads the right IFilter driver for the file, then sucks all the text out. It was COM because the client program was a VB6 service.

This really rocked because all I had to do was find the right IFilter drivers for a document format and away it went... no configuration, just joy. And lest you think this is all old news and worthless, Microsoft's Desktop Search still uses IFilter magic. Google Desktop Search also can use IFilters with the appropriate plug-in. These days you can find plug-ins for everything from AutoCAD drawings to MP3 files (not to mention all the big-boys, like Word, WordPerfect, OpenOffice, PDF, etc).

So with the recent discussion on the ADVANCED-DOTNET list, I offered and was asked to dust off the component and publish it for your use. I had to do a tiny bit of cleanup and conversion to be usable with Visual Studio, but it's up for your pleasure. It can be used from .Net programs, of course, though I might eventually get around to rewriting the tiny amount of real code as a .Net library. I haven't looked into the quality of this, but there is something on CodeProject.

Download here.

Monday, December 11, 2006

What's wrong with this code...

private void EnsurePropsOwned()
{
    if (!this.propsOwned)
    {
        this.propsOwned = true;
        if (this.properties != null)
        {
            PropertyDescriptor[] descriptorArray1 = new PropertyDescriptor[this.Count];
            Array.Copy(this.properties, 0, descriptorArray1, 0, this.Count);
            this.properties = descriptorArray1;
        }
    }
   
    if (this.needSort)
    {
        this.needSort = false;
        this.InternalSort(this.namedSort);
    }
}
  1. If you're going to take ownership of something, wouldn't it be nice to grab a copy BEFORE you claim to own it?
  2. If you are going to sort something, don't you think you should actually sort it before clearing needs-sort flag?
The scary part is that this is code from System.ComponentModel.PropertyDescriptorCollection which is something deep inside the framework and completely impossible to debug for threading issues.

I would just report this as a bug to Microsoft, but since they have a history of just ignoring bugs (or worse yet, just closing them without reason), I'm not going to waste my time. Not that I could today anyway, since Microsoft Connect doesn't (irony!)

Friday, December 08, 2006

Today in blinding science...

Teen girls who are frequent scale-steppers often have more weight problems.

Really? Who paid for this? Can we get the money back?  They couldn't even get the order of the causality right, much less isolate it from other dependencies.  Sheesh.

Thursday, November 30, 2006

How fast can a meme spread?

As a research project, Scott Eric Kaufman is seeing how fast a meme can spread. If you have a blog, please post a link to his post.

Friday, November 17, 2006

IL Visualizer gets better!

Haibo Luo's most-excellent tool for IL visualization just got a bunch better. I could not have built my Dynamic class without this tool, it's just plain awesome.

The new version adds support for display of RuntimeMethodInfo and RuntimeConstructorInfo visualization, support for Delegate.MethodInfo visualization.  Perhaps coolest of all it now has the ability to incrementally show the method's IL being built with each click of the new context-menu option.

Thanks, Haibo!

Thursday, November 09, 2006

Best. Analogy. Ever.

Some of the magazines I get (and I get bunches) are expensive, some are free, some of those are occasionally worth the read. But rarely is the editorial of any of them worth the soy it's printed with. Imagine my surprise when I found this editorial in the November, 2006 issue of Windows IT Pro:

You buy a house. After you move in, your walls and floors are suddenly soaking wet because all the pipes are leaking. You learn that your builder is infamous for constructing popular houses that have porous plumbing. Then you find some plumbing companies that specialize in fixing your builder's pipes. These plumbers have become hugely successful by understanding how to cut holes in your walls, access the pipes, fix them in whatever way they feel is appropriate, then charge you for getting rid of the leaks your builder was responsible for. The plumbers might have knocked holes in your walls, but at least you're no longer drowning.

Naturally, you join your neighbors in demanding that your builder stop constructing houses with dangerous plumbing. After years of complaints, the builder finally sees the light and revamps the whole plumbing system. The builder also realizes that when plumbers need to work on the pipes (as they inevitably must), whacking holes in random walls isn't the best approach and burglars could also use those holes to plunder the house. So the builder decides to create access panels through which plumbers can reach the pipes but that shut burglars out.

Everybody lives happily ever after, right? Not really. Plumbers are outraged: Not only has the builder eliminated a huge plumbing market by constructing houses with better pipes, but the builder is also preventing the plumbers from taking the quickest, easiest route to reaching the pipes. No more knocking holes in walls. How dare the builder improve its product in ways that prevent other businesses from profiting from the product's defects?

Karen Forster, be proud! You've captured completely the attitudes of McAfee and Symantec to the Kernel Patch Protection built into Vista x64. Personally, I'm happy to see both of those cruft-ware companies experience as much pain as possible, given the number of PC's I've had to clean up (and how difficult that is) after one of thier suites has inserted tenticles into every nook and cranny of some machine (now no longer functional). I'm still mad about the way McAfee broke pages with largish-viewstate, and the way that went unfixed for literally years.

If you want to know who will play along, Trend-Micro is happy to use Microsoft's new APIs, and I'm a customer because of that respect.

Tuesday, November 07, 2006

So how exactly is it that I'm faster than the C# compiler anyway?

I've had this one in the hopper for a while, but recently people have been asking, so...

I was curious about why my Dynamic Reflection Library code was benchmarking as 6% faster than native code (e.g. that produced bu the C# compiler) for reference types, so I did a little disassembly and grokking. Note, I did remember to make sure that the Supress JIT optimization on module load option was not checked and this is a release build with compile-time optimizations enabled.

Here's the native-call code with for a single compile-time call T child = firstEntry.Breed(secondEntry, (Gender)neighbor);:

0000037a mov eax,dword ptr [ebp-38h] 
0000037d mov dword ptr [ebp-70h],eax
00000380 push 0
00000382 push 0
00000384 push 0
00000386 mov ecx,esi
00000388 mov edx,0CA000057h
0000038d call 7919286A
00000392 push edi
00000393 mov edx,dword ptr [ebp-70h]
00000396 mov ecx,dword ptr [ebp-30h]
00000399 nop
0000039a nop
0000039b nop
0000039c call dword ptr [eax]

Now for the explicit generics call for the matching Explicit call T child = firstEntry.Breed(secondEntry, (Gender)neighbor);:

0000046a push esi 
0000046b push ebx
0000046c mov edx,edi
0000046e mov ecx,dword ptr [ebp-60h]
00000471 mov eax,dword ptr [ecx+0Ch]
00000474 mov ecx,dword ptr [ecx+4]
00000477 call eax

That's tons simpler, how come? The simple answer is that since the native call code is dealing with an interface, the code has to go through an indirect call. Meanwhile, the generated delgate code (what my Dynamic class creates) gets JITted out of existence and replaced with a direct call to the actual method on the destination class (Person.Breed in this case). Similar savings all through the test method body leave even more opportunity to keep the locals in registers and the lack of indirect calls saves a bunch of memory/cache-line access per-loop pass.

For my benchmark program (itself an interesting read) the BenchCallDynamicExplict<Person>(Adjuster<Person>, Random rnd) method loop code (not counting setup and teardown) is 0x1F3 bytes in JIT form, the BenchCallCompileTime<Person>(Adjuster<Person>, Random rnd) loop code is 0x2E5 bytes, which is 252 bytes longer.

Looking further at this code, if I constrain the T to be a class (e.g. a reference-type not a value-type/struct) then I get exactly the same code, so there's no point in that, I modified the benchmark code to remove the "hack" that required a new() constraint and passed an progenitor instance to eliminate the disadvantage for the compile-time (which needs to go through an interface). Even that doesn't change the code significantly.

The moral of this exercise is that sometimes the generated code, by giving much clearer information to the JIT process about exactly what methods are being called will allow it to generate much faster code. I'm not saying that you should do this kind of optimization everywhere, or indeed almost anywhere, but I am saying that when you write low level code to simplify your life and make things faster, you sometimes get much more than you can expect as a benefit.

Take that C# compiler, I win! Oh, and you guys on the JIT team... you rock!

Microsoft .Net Framework 3.0 now released

Not that I like the "version number" any more than I like the whole Web 2.0 thing, but YAY!

Get the goods here.

Monday, November 06, 2006

Strawberry or Blueberry

With elections one day away (finally!), I was actually brought to tears of laughter by this lovely Ze Frank episode.

Me, I have grown to  hate both blueberries and strawberries, though one of the strawberries seems to be pretty good... of course he's fighting a real peach of a blueberry, who I would rather see unemployed than doing the crappy job so far...

Speaking of which, if you want a real auditor, wouldn't it make sense to pick an accountant that isn't a strawberry or blueberry? Maybe we should look a little deeper for some unique flavor.

I think I'm going off the board for a bit of Buddha's Hand and vote for Terry Bunker for Missouri state auditor.

Of course, that's just my opinion, feel free to think for yourselves...

Friday, November 03, 2006

Dynamic Reflection Library release 2.5.0.0 on CodePlex

I've updated my Dynamic LCG-based Reflection wrapper tonight. This new version finally has extensive XML documentation in place. I also refactored a little bit of the code to eliminate duplicate calls and sped up the code a tiny bit.

As of this release, I'm actually 6% faster than native calls (e.g. the code emitted by the C# compiler is actually slower) against reference-types when doing explicit binding to methods. This means that there is realistically little to no cost doing name-based binding to arbitrary methods at runtime.

Head on over to the CodePlex project for more information and the downloads.

Thursday, November 02, 2006

DuckTyping for existing classes

First off, if you don't lurk on CodeProject, I mock you... but assuming you either don't, or just missed this one, Günter Prossliner posted a great bit of code that lets you dynamically "implement" any interface against a class instance by at runtime building a shim class that forwards all the calls through to matching methods in the target.

This is what is commonly called DuckTyping, and allows you to program "as if" an object implements any arbitrary interface, even if it really doesn't inherit from that class. In my Dynamic library, I let you do that for a single method at a time. Günter's code lets you do it an interface at a time, which is obviously cooler if you have multiple methods that you expect a class to implement (and for which you can define an interface).

I think my next weekend project will be to port his code, which uses dynamic assemblies, to use my Dynamic library instead (and thus benefit from LCG).

I've got a ways to go, world-wide though...

Here's where I've spent at least one night by country.


create your own visited countries map or vertaling Duits Nederlands

Alaska is next... via motorcycle. hopefully.

I've slept in all of these states at least once:


create your own visited states map or check out these Google Hacks.

[Via: Brad]

Friday, October 27, 2006

Observations about Concurrency from the master...

If lock, Monitor.Enter, Interlocked.Increment and other such keywords and methods hold any interest to you, stop reading my drivel now and read Joe Duffy's latest solid-gold hit Concurrency and the impact on reusable libraries

Thursday, October 26, 2006

Looking for a new job?

I keep hearing that nobody can find a decent developers. Of course finding decent jobs can be a bit tough, but now the fine guys at The Daily WTF have come up with a great way to syndicate job listings through blogs to directly target the kind of people that might be interested (based on the fact that you are reading my blog).

I've signed up, and any commissions I receive will be donated to Project Gutenburg. So, if you know anyone that wants to list a job, point them here, and if you are looking for a job, check my blog side-bar or footer, or browse the listings here.

Thursday, October 19, 2006

Internet Explorer 7, bogus repros on Secunia and Midnight Madness

By now you all realize that IE7 has been released on the 18th. And now we have a report of the first vulnerability. This one is purported to be a problem with the way that IE handles redirections that specify the mhtml: URI handler. The problem may exist or not, but I can tell you that the "test" for it over on Secunia is flawed at best. I watched the "test" of IE7 RTM under Fiddler, what it does is use an XMLHttpRequest to do a request from their site, which returns a 302 Found redirect requesting mhtml:http//... (again from their site), which then gets processed (not sure what that indicates, and what the point of the mhtml URI handler is, other than the way that we're supposed to lose track of the hosting domain, but whatever). That request returns another 302 Found redirect to http://news.google.com, then they test the response to see if it contains "news.google" in it. However, my browser never followed that other redirect, so I can't fathom how their test is remotely valuable. Fiddler clearly shows only two requests. Here's the complete session after clicking the "test it" link:

------------------------------------------------------------------
GET /ie_redir_test_1/?0.9495259804243511 HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer: http://secunia.com/Internet_Explorer_Arbitrary_Content_Disclosure_Vulnerability_Test/
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Host: secunia.com
Proxy-Connection: Keep-Alive
Cookie: SECUNIA=XRdqbl8qUUgk9kpJInxPBmogDpbYp0RNS5E3rMtL;
__utma=25207103.253302053.1161293305.1161293305.1161293305.1;
__utmb=25207103; __utmc=25207103;
__utmz=25207103.1161293305.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none)


HTTP/1.1 302 Found
Date: Thu, 19 Oct 2006 21:41:11 GMT
Server: Apache
Location: mhtml:http://secunia.com/ie_redir_test_2
Transfer-Encoding: chunked
Content-Type: text/html

0

------------------------------------------------------------------
GET /ie_redir_test_2 HTTP/1.1
Accept: */*
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Host: secunia.com
Proxy-Connection: Keep-Alive
Cookie: SECUNIA=XRdqbl8qUUgk9kpJInxPBmogDpbYp0RNS5E3rMtL;
__utma=25207103.253302053.1161293305.1161293305.1161293305.1;
__utmb=25207103; __utmc=25207103;
__utmz=25207103.1161293305.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none)


HTTP/1.1 302 Found
Date: Thu, 19 Oct 2006 21:41:11 GMT
Server: Apache
Location: http://news.google.com/
Transfer-Encoding: chunked
Content-Type: text/html

0

--------------------------------------------------------------------

I've been running the recent release candidates for a while now, and I really like it. It troubles me that this sort of "reporting" is already happening. I still remember "staying up till midnight" to download it way back when IE 3.0 was released on August 13, 1996 (bringing CSS support, ActiveX, Java applets, inline multimedia t to a mainstream browser for the first time!), and I still have the t-shirt.

In fact a few years ago that shirt almost got me in some trouble. I had several years earlier bought a whole bunch of dumb terminals (Televideo 950's to be precise) and modems (U.S. Robotics 1200 baud!) that I had been bundling with appropriate cables to use as BBS machines. I made a killing, buying nearly a hundred terminals and modems and tons of other hardware for a total of $56. On average I got $20 per "setup", and sold a bunch of the other stuff too. The problem was that I bought far more than I could ever sell, and the Internet was starting to catch on an BBS's were going out of fashion. While you could use a dumb-terminal for PINE and LYNX on a shell account, my market was drying up.

After a few years of moldering in the basement, my wife handed down the law and I was tasked with ridding myself of the excess. The problem, until then unforeseen, was that dumb terminals are fancy televisions, and televisions (in those days) contain CRT tubes. CRT tubes contain phosphor and phosphor is considered hazardous waste. No wonder they wanted to get rid of all those dumb terminals!

So, left with the the prospect of paying large amounts of money to get them disposed of properly, and not having all the cool resources we have these days, I did what any young and stupid guy would do. I threw them away. No in my garbage, of course... no, I went from strip mall to strip mall, tossing a couple terminals in random dumpsters. Knowing that this would look a tad suspicious, I (being young and stupid) decided to wear black jeans, black shirt, and do this at 3 in the morning. That's where the I Downloaded t-shirt comes into the story...

I chose that shirt because it was black... new moon black... shadow black... what I didn't realize was that the cool lettering and logos printed on the shirt were glow-in-the-dark ink. So, there I was, in the back of a strip mall, with my car idling but the lights off, under a new moon wearing black pants and a black shirt that was glowing, declaring brightly that "I downloaded Midnight August 13, 1996" on the back and "Midnight Madness" on the front.

Madness, indeed.

Friday, September 29, 2006

Dynamic Reflection Library now hosted on CodePlex

For your ease of access and better place for any bug-reports or enhancement requests, I've placed my Dynamic library on CodePlex.

Monday, September 25, 2006

If you need a laugh...

Sometimes just listening to something funny can brighten your day.  Here's a couple great sources of fun:

http://www.millahseconds.com/ - Mark Miller's quick fun...

http://www.thesmartestman.com/ - Rory goes audio...

Friday, September 15, 2006

He's ALIVE!

It was the best of times, he's posted again:

It's been over two years since I blogged.

It's was the worst of times:

...I left the CLR team and the Developer Division

So it seems that the only way to keep on learning from the one guy responsible for me being interested in .Net development is to work with him:

In the meantime, if any readers are interested in working on a deep system incubation with me...

Christian, we've missed you, and evidentially still will. I remain saddened by your absence.

[via: A quick update on me.]

Miniature Earth

Percentages are boring, but what if you mapped many statistics about the world population down to a population of 100. What would the world look like. Check out this presentation on the miniature Earth.

Tuesday, September 12, 2006

Team Foundation Server is gaining traction, do you know it well enough to teach?

I'm very pleased with my experiences thus far with Team Foundation. It's version control tools are excellent, the work item stuff is very flexible, but in truth I haven't worked with much of the rest of it. There's all the portal stuff, the guidance stuff, the diagramming tools. All very cool, but not quite on my radar.

So today, when I got a call from a recruiter looking for someone to mentor a team in the effective use of TFS, I knew I wasn't the guy for the job... not to mention that I'm happy doing what I'm doing, and I'm not interested in relocating out of St. Louis for anyone.

What I want to know is if anyone out there is interesting in a 6 month mentoring gig in Harlesville, PA mentoring 20-25 people in the effective use of TFS feel free to contact me or Lynne Trottnow for more information.

To the Microsoft TFS team, I say "job well done!".

Thursday, September 07, 2006

Who do they really care about? Not me..

Countless security holes go weeks without being addressed, I've had many .Net bug reports closed with Won't-Fix due to "time constraints" before the nearly-year-long-coming service pack, yet a DRM flaw gets patched in 3 days. Does anyone REALLY think that Microsoft's priorities are remotely related to what is good for the customer?

Microsoft issues Quickest Patch Ever

Tuesday, September 05, 2006

The easy way to prep a solution for sharing

How many times have you downloaded a .Net project from somewhere and unzipped it to find all kinds of kruft that should NEVER have been shipped to anyone? Things like .SUO files, .USER files, the obj and bin directories? Of course we sometimes forget to clean the solution out before zipping, or forget to prune the zip file (my personal strategy). Wouldn't it be cool to just automate that process in the fine tradition of lazy smart programmers everywhere?

Head over to The Code Project and check out SolutionZipper, a Visual Studio 2005 Addin that cleans and zips a Solution in one step.

Thanks Robert Nadler!

Wednesday, August 23, 2006

Friday, August 18, 2006

How to do late Dynamic method creation

In this post comment, I was asked about how to do creation of method delegates whose types can vary somewhat when using the Dynamic class. It's really a matter of using the unspecified form of a generic class and the Type.MakeGenericType method to synthesize up the desired fully specified type.  This code is going to look ugly, simply because playing with generics this way is more akin to writing a compiler than writing normal code.  Much of this code is boiler-plate and could easily be extracted into a set of helper methods... given some time, I'll do so in my current Dynamic class. Until then, here's an example of what things can look like:

// needed by both versions
BindingFlags creatorFlags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static;
Type staticProc = typeof(Dynamic<>.Static.Procedure.Explicit<,,>);
 
// this version "knows" the final argument type of the method, and could have been done explicitly
// I show this to make obvious the way you use MakeGenericType.
Type doubleProc = staticProc.MakeGenericType(typeof(PutMethods), typeof(IIdentifiable), typeof(string), typeof(double));
MethodInfo doubleProcCreateDelegate = doubleProc.GetMethod("CreateDelegate", creatorFlags, null, new Type[] { typeof(string) }, null); 
StaticProc<PutMethods, IIdentifiable, string, double> proc =
   doubleProcCreateDelegate.Invoke(null, new object[] { "Add" }) as StaticProc<PutMethods, IIdentifiable, string, double>
proc(null, "Age", 1.0);
 
// this is more like a normal use, where the final argument type is not known at delegate generation time...
Type lazyProc = staticProc.MakeGenericType(typeof(PutMethods), typeof(IIdentifiable), typeof(string), typeof(object));
MethodInfo lazyProcCreateDelegate = lazyProc.GetMethod("CreateDelegate", creatorFlags, null, new Type[] { typeof(string) }, null);
StaticProc<PutMethods, IIdentifiable, string, object> lazy =
   lazyProcCreateDelegate.Invoke(null, new object[] { "Add" }) as StaticProc<PutMethods, IIdentifiable, string, object>
lazy(null, "Balance", -10.0);

  
public interface IIdentifiable
{
   void SetProperty(string propertyName, object value);
} 
 
public class PutMethods
{
   public static void Add(IIdentifiable dr, string propertyName, double value)
   {
      if (dr != null)
         dr.SetProperty(propertyName, value);
   }
}

Note that you can easily build up the Type[] passed to Type.MakeGenericType based on things like an attribute class or configuration/XML markup.

Tuesday, August 15, 2006

The step-child gets some love...

Looks like Google is finally going to update Blog*Spot (a.k.a. Blogger in hosted mode).  Coming soon:

  • Dynamic content (not static pages) so no more republishing.
  • Access Control so you control who can read your blog.
  • Labels to let you tag the posts (why they couldn't call them tags, like the rest of the world, I don't know).
  • Easy layout to let people that don't get CSS or HTML markup do drag-and-drop stying of the blog.
  • More feed formats so you can choose the format you want to publish, and the default of Atom 0.3 will change to Atom 1.0.

I'm waiting anxiously to migrate this blog there and start tagging labelling things.

More information here.

Monday, August 14, 2006

Trying out Windows Live Writer...

I've used "Send to Blogger...", I've used "w:Blogger", so why not try Microsoft's latest stab at Googledom?  Looks neat so far. You can get more information over at Introducing Windows Live Writer

Tuesday, August 08, 2006

They're back!

Ahhh, the warm feelings of nostalgia wave over me... in less than a month, I get to see if the sweet memory is a suite reality. Check out the New Turbo C++/Turbo Delphi/Turbo C# homepage.

Putting it all together...

Jeremy D. Miller recapitulates the Best of the Shade Tree Developer. I nominate this as the most useful post of the year.

Wednesday, August 02, 2006

Yield to the power of the DataSource

The new declarative model for data sources in ASP.Net 2.0 is very seductive in its simplicity. I've all-too-often seen people doing date-picker and time-pickers by listing the times individually in ListItems. This leads to pages that have to be manually revisited if you change the range (start-time and end-time) or the increment (e.g. on-the-hour, on-the-half, etc.). It would be really cool to be able to drive these lists declaratively and bind them using the standard DataSourceID logic.

Using the new C# yield keyword it is trivial to return an IEnumerable<DateTime> that lists all the times (with a specified increment in minutes), days, weeks, months, etc. in a date range. I give you DateTimeDataSource:

using System;
using System.Collections.Generic;
using System.ComponentModel;
 
[DataObject(true)]
public class DateTimeDataSource
{
    private TimeSpan _DayStart = TimeSpan.FromHours(8); // default to 8am
    private TimeSpan _DayEnd = TimeSpan.FromHours(18); // default to 6pm
 
    [Browsable(true)]
    public TimeSpan DayStart
    {
        get { return _DayStart; }
        set { _DayStart = value; }
    }
 
    [Browsable(true)]
    public TimeSpan DayEnd
    {
        get { return _DayEnd; }
        set { _DayEnd = value; }
    }
 
    public static DateTime WeekStart(DateTime date)
    {
       return date.Date.AddDays(-(int)date.DayOfWeek);
    }
 
    public static DateTime WeekEnd(DateTime date)
    {
        return date.Date.AddDays(7).AddTicks(-1);
    }
 
    public static DateTime MonthStart(DateTime date)
    {
        return date.Date.AddDays(1 - date.Day);
    }
 
    public static DateTime MonthEnd(DateTime date)
    {
        return date.Date.AddMonths(1).AddTicks(-1);
    }
 
    public static DateTime YearStart(DateTime date)
    {
        return new DateTime(date.Year, 1, 1);
    }
 
    public static DateTime YearEnd(DateTime date)
    {
        return new DateTime(date.Year + 1, 1, 1).AddTicks(-1);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> ThisWeek()
    {
        DateTime start = WeekStart(DateTime.Today);
        DateTime end = WeekEnd(start);
        return Any(start, end, TimeSpan.FromDays(1));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> ThisMonth()
    {
        DateTime start = MonthStart(DateTime.Today);
        DateTime end = MonthEnd(start);
        return Any(start, end, TimeSpan.FromDays(1));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> Today(int minuteIncrement)
    {
        return Any(DateTime.MinValue, DateTime.MaxValue, minuteIncrement);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Today(TimeSpan increment)
    {
        return Any(DateTime.MinValue, DateTime.MaxValue, increment);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> Weeks(int numberOfWeeks)
    {
        DateTime start = WeekStart(DateTime.Today);
        DateTime end = start + TimeSpan.FromDays(7 * numberOfWeeks);
        return Weeks(start, end);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Weeks(DateTime start, int numberOfWeeks)
    {
        DateTime end = start + TimeSpan.FromDays(7 * numberOfWeeks);
        return Weeks(start, end);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Weeks(DateTime start, DateTime end)
    {
        return Any(start, end, TimeSpan.FromDays(7));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> Any(DateTime start, DateTime end, int minuteIncrement)
    {
        return Any(start, end, TimeSpan.FromMinutes(minuteIncrement));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Any(DateTime start, DateTime end, TimeSpan increment)
    {
        if (start == DateTime.MinValue || start.Ticks == 0)
        {
            start = DateTime.Today.Date + DayStart;
        }
 
        if (end == DateTime.MaxValue || end.Ticks == 0)
        {
            end = DateTime.Today.Date + DayEnd;
        }
 
        for (DateTime current = start; current <= end; current += increment)
        {
            yield return current;
        }
    }
}

Use this just like you would any other data source, for example, to build a RadioButtonList for the normal working hours (8am to 6pm, see notes below), you can do this:

<asp:ObjectDataSource ID="Hours" runat="server" CacheDuration="Infinite" EnableCaching="true"
        TypeName="DateTimeDataSource" SelectMethod="Today">
    <SelectParameters>
        <asp:Parameter Name="minuteIncrement" Type="Int32" Direction="Input" DefaultValue="30" />
    </SelectParameters>
</asp:ObjectDataSource>
<asp:RadioButtonList ID="RadioButtonTimeFrom" runat="server"
        DataSourceID="Hours" DataTextFormatString="{0:t}" />

Notes:

  • The properties for DayStart and DayEnd let you change the default start and end time for time-pickers.
  • The XXXXStart and XXXXEnd static methods are there to simplify the generation of date ranges.
  • This isn't necessarily completely correct with other calendar forms.

UPDATE: Blogger ate my || in the code of Any

Perma (as in PERMANENT) links that aren't annoy me...

Dear Microsoft Live.com team, Please stop assigning new link value to all the posts when you upgrade the software on Live.com. I really hate having to prune all the duplicates out of my RSS reader. Permalinks are supposed to be permanent, not "valid until further upgrades". Thank you and enjoy your day, Marc

Monday, July 31, 2006

The little guy helps out Microsoft

It seems Microsoft is having trouble paying the (bandwidth) bills and along comes the "little guy" to help them out. I've previously mentioned Red Swoosh, a BitTorrent-like product that lets you proxy your files automatically so they get downloaded from any peers that currently have the file (or parts). This very similar to BitTorrent; the differences are that the download happens inside your browser, it doesn't require special preperation (as the Red Swoosh site handles the seeding), and requires only a tiny download. In fact, the latest version 2.0 of the Red Swoosh client has shrunk from 600KB to 72 KB!

So, if you want to download the Office 2007 beta without paying Microsoft $1.50 to cover thier bandwidth costs, just click me. [Via: Get the Microsoft Office Professional 2007 Beta]

Friday, July 28, 2006

Fixing forums.asp.net RSS feed issues.

I use RSSBandit as my feed reader, and it is awesome. I noticed, however, that it was having issues with the forum feeds from ASP.Net. A typical feed from there looks like this http://forums.asp.net/rss.aspx?ForumID=1022&Mode=0 (not sure what that Mode=0 does... anyone care to document that). This works fine the first time I hit the page, but subsequent to that CommunityServer (which is what forums.asp.net runs on) will always return a 304 Not Modified HTTP status code. This is clearly a lie when the forum has new messages. For a while, I would just tweek the feed url changing the Mode parameter to 1 (no idea what this does), do a refresh, and then change it back. This would cause a URL mismatch, so we would get the feed from the server anew.

This got a little old after a while, so I fired up Fiddler and verified what I needed to tweek to insure the fetch succeeds. If I zap the If-Modified-Since and If-None-Match header elements from the request headers, then I'll always get back the feed. This is somewhat bad manners, of course, but until the folks at Telligent fix the problem in CommunityServer and the folks at Microsoft update the software at asp.net, I'll just have to keep poking them.

The code for Fiddler's script looks like this:

// up in the top of the class Handlers
public static RulesOption("Fix &RSS forums.asp.net")
var m_RSSForum: boolean = true;
 
// in OnBeforeRequest
if (m_RSSForum
    && oSession.host.indexOf("forums.asp.net")>-1
    && oSession.url.toLowerCase().indexOf("/rss.aspx")>-1)
{
    oSession.oRequest.headers.Remove("If-Modified-Since");
    oSession.oRequest.headers.Remove("If-None-Match");
}

Now that I run Fiddler all the time, this just works (which I do to strip ads from everything I surf). I hope this helps you.

Thursday, July 27, 2006

Updated files for Dynamic method caller available...

I've updated the Utilities.zip and DynamicComparer.zip files with my latest cleanup of the DynamicCall stuff. This version fully supports static methods is much simpler, uses less working set and is faster. A full post will follow on all the changes soon.

BREAKING CHANGE

This version has an entirely new syntax that I think is clearer, plus it leads to much better generic-specialization / JIT memory usage. The new syntax looks like this:

Func<Person, bool, Person> compatible = Dynamic<Person>.Instance.Function<bool>.Explicit<Person>.CreateDelegate("Compatible");
Func<Person, Person, Person, Gender> breed = Dynamic<Person>.Instance.Function<Person>.Explicit<Person, Gender>.CreateDelegate("Breed");
Proc<Person> mutate = Dynamic<Person>.Instance.Procedure.Explicit.CreateDelegate("Mutate");
StaticFunc<Person, int, Person, Person> ageDifference = Dynamic<Person>.Static.Function<int>.Explicit<Person, Child>.CreateDelegate("AgeDifference");
Or, if you use the new C# 3.0 var syntax:
var compatible = Dynamic<Person>.Instance.Function<bool>.Explicit<Person>.CreateDelegate("Compatible");
var breed = Dynamic<Person>.Instance.Function<Person>.Explicit<Person, Gender>.CreateDelegate("Breed");
var mutate = Dynamic<Person>.Instance.Procedure.Explicit.CreateDelegate("Mutate");
var ageDifference = Dynamic<Person>.Static.Function<int>.Explicit<Person, Child>.CreateDelegate("AgeDifference");
I'm sorry for any trouble this might cause, but I think it is worth it. If you have any comments, I'm listening...

UPDATE (27-July-2007 15:32): I changed the Build to skip access-checks when creating the DynamicMethod. This allows you to bind against private methods of classes even when the bound-to class is not on the inheritance path. Also fixed a bug with instance methods that are functions requiring no parameters not always binding correctly. If you downloaded before 15:30 CDT, please download again.

Tuesday, July 25, 2006

Things to remember when creating delegates and doing LCG...

In my AcquiringObjectDataSource articles part three I promised to show what things look like using the DynamicCall library I've previously posted. In doing that, I've had to make the DynamicCall stuff work with static methods. Previously I had inserted some code to start handling statics deep inside the Build methods, but I've certainly never gotten to the point of using them. Sorry to anyone that tried, I should have documented that code as not-yet working.

Anyway, last night I worked into the wee hours of the morning getting everything hooked up for static methods. In the process I learned some things that I thought I would dump now. In an upcoming post, I'll talk about the changes to the DynamicCall library, but for now:

  • When you create a delegate for a static method, the delegate signature should not include a "this" reference. Duh. This manifests itself in the error message below when calling CreateDelegate. This happens because the delegate definition expects a "this" pointer as the first method argument, but the generated DynamicMethod doesn't have one. This spawned a whole new set of StaticProc and StaticFunc delegate definitions.
    ArgumentException: Error binding to target method
  • If you get the delegate definition correct, but don't generate the right code, it'll hurl at JIT with the message below. Note that the generation of the LCG DynamicMethod is fine, the delegate binding is fine. The JIT of the first caller to that delegate is where things will pop. This is often very in the runtime, which is bad.
    System.InvalidProgramException: Common Language Runtime detected an invalid program.
  • Not strictly related to static method supprt, but I rediscovered that you can't access private members of a class unless the DynamicMethod was properly bound to that class. This resulted in me still having to know the class of the static method, even though there's no "this" pointer to deal with.
  • Ruthless refactoring makes things much easier. Doing this work on the DynamicCall library has really cleaned things up, I'll give more detail in a later post.

As it stands now binding to a static method now looks like this:

StaticProc<ObjectDataSourceView, ParameterCollection, IDictionary, IDictionary> s_MergeDictionaries =
    DynamicProcedure<ObjectDataSourceView, ParameterCollection, IDictionary, IDictionary>.Static.Initialize("MergeDictionaries", CreateParameterList.Auto);
    Dynamic<ObjectDataSourceView>.Static.Procedure.Explicit<ParameterCollection, IDictionary, IDictionary>.CreateDelegate("MergeDictionaries", CreateParameterList.Auto);

While this is really clear (I think), it is very verbose. The new C# 3.0 "var" feature will will make this much more readable:

var s_MergeDictionaries =
    DynamicProcedure<ObjectDataSourceView, ParameterCollection, IDictionary, IDictionary>.Static.Initialize("MergeDictionaries", CreateParameterList.Auto);
    Dynamic<ObjectDataSourceView>.Static.Procedure.Explicit<ParameterCollection, IDictionary, IDictionary>.CreateDelegate("MergeDictionaries", CreateParameterList.Auto);

UPDATE (27-July-2007 15:32): New syntax based on the latest round of refactorings on the DynamicCallDynamic<> class.

Monday, July 24, 2006

I can see clearly now, the pain is gone (or how one little virtual makes the whole view clear)

In part one of this little series, I introduced the first player in our little play, AcquringObjectDataSource. Quite literally, the only reason that class was necessary was to insure that we create a slightly smarter ObjectDataSourceView. This part of the series is all about adding the "acquire" behavior. What I'm after is a simple way of getting the choice as to whether the DataObjectType instance is created ex-nilo via Activator.CreateInstance (the standard behavior of ObjectDataSourceView, or created by a factory class, or recalled from some cache or datastore. In my case, I want to reload the object instances from the database via Wilson's O/R Mapper, after first having checked the ASP.Net Session to see if we've got partial changes to carry through.

The View does the work

The first thing that becomes painfully obvious when building on ODS is that the ODSV object does the heavy lifting. Almost everything in ODS that isn't related to the creation of the view, or the maintenance of the cache is merely a call-through proxy to the ODSV. The main operations are obviously the CRUD operations. These operations are carried out but the ExecuteInsert, ExecuteSelect, ExecuteUpdate and ExecuteDelete, respectively. The read method (ExecuteSelect) already acts as expected, so we'll ignore that method. The others mutating methods are handed an IDictionary of keys, old values and new values (each method gets some of these, as appropriate to the use). The dictionaries are used to fill in values for a new object (in case of insert), an old object (in case of delete), or both (in case of update). This is, as stated, where my pain begins. I want to insure that we hit whatever the original data source is if needed, not create the objects out of the ether. In each of the mutating methods it builds up a list of parameter values by merging in any hard-coded parameters values, the keys IDictionary and any values IDictionary(s). This is done by a private method of ODSV called BuildDataObject. This simple method looks like this in Reflector (I've highlighted the line that grieves my soul):

private object BuildDataObject(Type dataObjectType, IDictionary inputParameters)
{
      object obj1 = Activator.CreateInstance(dataObjectType);
      PropertyDescriptorCollection collection1 = TypeDescriptor.GetProperties(obj1);
      foreach (DictionaryEntry entry1 in inputParameters)
      {
            string text1 = (entry1.Key == null) ? string.Empty : entry1.Key.ToString();
            PropertyDescriptor descriptor1 = collection1.Find(text1, true);
            if (descriptor1 == null)
            {
                  throw new InvalidOperationException(SR.GetString("ObjectDataSourceView_DataObjectPropertyNotFound", new object[] { text1, this._owner.ID }));
            }
            if (descriptor1.IsReadOnly)
            {
                  throw new InvalidOperationException(SR.GetString("ObjectDataSourceView_DataObjectPropertyReadOnly", new object[] { text1, this._owner.ID }));
            }
            object obj2 = ObjectDataSourceView.BuildObjectValue(entry1.Value, descriptor1.PropertyType, text1);
            descriptor1.SetValue(obj1, obj2);
      }
      return obj1;
}
If it weren't for that simple little Activator.CreateInstance, my life would have been easy... but, alas, I must toil against the oppressors. I could easily replace that method, but for several things. First, it is not virtual, so my version would not get called. Second, it is private, so I can't very well call down to reuse the behavior. Third, it uses ObjectDataSourceView.BuildObjectValue, which is of course private.

Virtual is where the action is

What I need to do is make sure that I have an integration-point where I can embrace and extend the Microsoft implementation. I want to make sure that some event or virtual method is called to create / rehydrate / lookup the actual data object. How about something blindingly obvious like:

public virtual object AcquireDataObject(Type dataObjectType, IDictionary inputParameters)
{
    return Activator.CreateInstance(dataObjectType);
}
This has the advantage of being hookable, while still letting the original naive implementation to be used. [Note: If Microsoft had simply done this and ONLY this to the original ODSV class, I wouldn't have needed any of this, not that I'm bitter]. We pass the IDictionary of input parameters to the creation method to give it a place to extract the key values, if needed.

Of course, it can't be that easy for me. Rather, I get bit by the fact that since BuildDataObject is non-virtual, private and pretty much incestuously coupled with everything else in ODSV, I'm going to have to do much more work. Specifically, I need to insure that my version of BuildDataObject gets called, so that I can replace that first line with a call to the new virtual AcquireDataObject method. This means that I need to override the mutating methods ExecuteInsert, ExecuteUpdate and ExecuteDelete. Since I only care about the case where a DataObjectType has been specified, I can down-call if that value isn't set, and have a vastly simplified body for each of these that only handles the DOT case. Of course, to simply reimplement those three methods I have to call a boat-load of other methods in ODSV, which are (you guessed it) private. Namely I need access to the Owner field, the GetType method, the TryGetDataObjectType method, the MergeDictionaries method, the GetResolvedMethodData method, the previously mentioned BuildObjectValue method, the InvokeMethod method, and ickly-enough the private ObjectDataSourceMethod nested class; whose Parameters field we also need to fiddle with. Lastly, we need to be able to extract the AffectedRows of the private ObjectDataSourceResult nested class. That's a lot of tendrils stuck in to ODSV, but I for the want of a single virtual, that is the path I must tread.

Where we are now

So, without any further delay, may I introduce to you the fair and fleeting AcquiringObjectDataSourceView, whose merest existence I groan at:

public class AcquiringObjectDataSourceView : ObjectDataSourceView
{
    const BindingFlags NeedlesslyPrivate = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
    const BindingFlags NeedlesslyPrivateStatic = BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly;
 
    static readonly FieldInfo s_Owner;
    static readonly MethodInfo s_GetType;
    static readonly MethodInfo s_TryGetDataObjectType;
    static readonly MethodInfo s_MergeDictionaries;
    static readonly MethodInfo s_GetResolvedMethodData;
    static readonly MethodInfo s_InvokeMethod;
    static readonly MethodInfo s_BuildObjectValue;
    static readonly FieldInfo s_AffectedRows;
    static readonly FieldInfo s_Parameters;
 
    static AcquiringObjectDataSourceView()
    {
        s_Owner = typeof(ObjectDataSourceView).GetField("_owner", NeedlesslyPrivate);
 
        Type[] getType = new Type[] { typeof(string) };
        s_GetType = typeof(ObjectDataSourceView).GetMethod("GetType", NeedlesslyPrivate, null, getType, null);
 
        Type[] tryGetDataObjectType = new Type[] { };
        s_TryGetDataObjectType = typeof(ObjectDataSourceView).GetMethod("TryGetDataObjectType", NeedlesslyPrivate, null, tryGetDataObjectType, null);
 
        Type[] mergeDictionaries = new Type[] { typeof(ParameterCollection), typeof(IDictionary), typeof(IDictionary) };
        s_MergeDictionaries = typeof(ObjectDataSourceView).GetMethod("MergeDictionaries", NeedlesslyPrivateStatic, null, mergeDictionaries, null);
 
        Type[] getResolvedMethodData = new Type[] { typeof(Type), typeof(string), typeof(Type), typeof(object), typeof(object), typeof(DataSourceOperation) };
        s_GetResolvedMethodData = typeof(ObjectDataSourceView).GetMethod("GetResolvedMethodData", NeedlesslyPrivate, null, getResolvedMethodData, null);
 
        Type[] buildObjectValue = new Type[] { typeof(object), typeof(Type), typeof(string)};
        s_BuildObjectValue = typeof(ObjectDataSourceView).GetMethod("BuildObjectValue", NeedlesslyPrivateStatic, null, buildObjectValue, null);
 
        Type objectDataSourceMethod = typeof(ObjectDataSourceView).GetNestedType("ObjectDataSourceMethod", BindingFlags.NonPublic);
        s_Parameters = objectDataSourceMethod.GetField("Parameters", NeedlesslyPrivate);
 
        Type[] invokeMethod = new Type[] { objectDataSourceMethod };
        s_InvokeMethod = typeof(ObjectDataSourceView).GetMethod("InvokeMethod", NeedlesslyPrivate, null, invokeMethod, null);
 
        Type objectDataSourceResult = typeof(ObjectDataSourceView).GetNestedType("ObjectDataSourceResult", BindingFlags.NonPublic);
        s_AffectedRows = objectDataSourceResult.GetField("AffectedRows", NeedlesslyPrivate);
    }
 
    public AcquiringObjectDataSourceView(ObjectDataSource owner, string name, HttpContext context)
        : base(owner, name, context)
    {
    }
 
    protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues)
    {
        if (!this.CanDelete)
        {
            throw new NotSupportedException(ExposedSR.GetString(ExposedSR.DeleteNotSupported, this.Owner.ID));
        }
 
        // we only change the behavior of sources that provide a DataObjectTypeName
        if (String.IsNullOrEmpty(this.DataObjectTypeName))
            return base.ExecuteDelete(keys, oldValues);
 
        Type sourceType = this.GetType(this.TypeName);
        Type dataObjectType = this.TryGetDataObjectType();
        IDictionary deleteParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
 
        if (this.ConflictDetection == ConflictOptions.CompareAllValues)
        {
            if (oldValues == null || oldValues.Count == 0)
            {
                throw new InvalidOperationException(ExposedSR.GetString(ExposedSR.Pessimistic, ExposedSR.GetString(ExposedSR.Delete), this.Owner.ID, "oldValues"));
            }
 
            MergeDictionaries(this.DeleteParameters, oldValues, deleteParameters);
        }
 
        MergeDictionaries(this.DeleteParameters, keys, deleteParameters);
        object dataObject = this.BuildDataObject(dataObjectType, deleteParameters);
 
        object deleteMethod = this.GetResolvedMethodData(sourceType, this.DeleteMethod, dataObjectType, dataObject, null, DataSourceOperation.Delete);
        IOrderedDictionary parameters = ExtractMethodParameters(deleteMethod);
        ObjectDataSourceMethodEventArgs args = new ObjectDataSourceMethodEventArgs(parameters);
        this.OnDeleting(args);
 
        if (args.Cancel)
        {
            return 0;
        }
 
        object result = this.InvokeMethod(deleteMethod);
 
        this.Owner.InvalidateCache();
        this.OnDataSourceViewChanged(EventArgs.Empty);
        return ExtractAffectedRows(result);
    }
 
    protected override int ExecuteInsert(IDictionary values)
    {
        if (!this.CanInsert)
        {
            throw new NotSupportedException(ExposedSR.GetString(ExposedSR.InsertNotSupported, this.Owner.ID));
        }
 
        // we only change the behavior of sources that provide a DataObjectTypeName
        if (String.IsNullOrEmpty(this.DataObjectTypeName))
            return base.ExecuteInsert(values);
 
        Type sourceType = this.GetType(this.TypeName);
        Type dataObjectType = this.TryGetDataObjectType();
 
        if (values == null || values.Count == 0)
        {
            throw new InvalidOperationException(ExposedSR.GetString(ExposedSR.InsertRequiresValues, this.Owner.ID));
        }
 
        IDictionary insertParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
        MergeDictionaries(this.InsertParameters, values, insertParameters);
        object dataObject = this.BuildDataObject(dataObjectType, insertParameters);
 
        object insertMethod = this.GetResolvedMethodData(sourceType, this.InsertMethod, dataObjectType, null, dataObject, DataSourceOperation.Insert);
        IOrderedDictionary parameters = ExtractMethodParameters(insertMethod);
        ObjectDataSourceMethodEventArgs args = new ObjectDataSourceMethodEventArgs(parameters);
        this.OnInserting(args);
 
        if (args.Cancel)
        {
            return 0;
        }
 
        object result = this.InvokeMethod(insertMethod);
 
        this.Owner.InvalidateCache();
        this.OnDataSourceViewChanged(EventArgs.Empty);
        return ExtractAffectedRows(result);
    }
 
    protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
    {
        if (!this.CanUpdate)
        {
            throw new NotSupportedException(ExposedSR.GetString(ExposedSR.UpdateNotSupported, this.Owner.ID));
        }
 
        // we only change the behavior of sources that provide a DataObjectTypeName
        if (String.IsNullOrEmpty(this.DataObjectTypeName))
            return base.ExecuteUpdate(keys, values, oldValues);
 
        object updateMethod;
        Type sourceType = this.GetType(this.TypeName);
        Type dataObjectType = this.TryGetDataObjectType();
 
        if (this.ConflictDetection == ConflictOptions.CompareAllValues)
        {
            if (oldValues == null || oldValues.Count == 0)
            {
                throw new InvalidOperationException(ExposedSR.GetString(ExposedSR.Pessimistic, ExposedSR.GetString(ExposedSR.Update), this.Owner.ID, "oldValues"));
            }
 
            IDictionary oldParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
            MergeDictionaries(this.UpdateParameters, oldValues, oldParameters);
            MergeDictionaries(this.UpdateParameters, keys, oldParameters);
            object oldDataObject = this.BuildDataObject(dataObjectType, oldParameters);
 
            IDictionary newParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
            MergeDictionaries(this.UpdateParameters, oldValues, newParameters);
            MergeDictionaries(this.UpdateParameters, keys, newParameters);
            MergeDictionaries(this.UpdateParameters, values, newParameters);
            object newDataObject = this.BuildDataObject(dataObjectType, newParameters);
 
            // if oldDataObject and newDataObject are the same object, this is a bit odd... but since we
            // built them old-then-new, the resulting object has the correct values. In general we aren't
            // going to be running that way.
 
            updateMethod = this.GetResolvedMethodData(sourceType, this.UpdateMethod, dataObjectType, oldDataObject, newDataObject, DataSourceOperation.Update);
        }
        else
        {
            IDictionary updateParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
            MergeDictionaries(this.UpdateParameters, oldValues, updateParameters);
            MergeDictionaries(this.UpdateParameters, keys, updateParameters);
            MergeDictionaries(this.UpdateParameters, values, updateParameters);
            object dataObject = this.BuildDataObject(dataObjectType, updateParameters);
 
            updateMethod = this.GetResolvedMethodData(sourceType, this.UpdateMethod, dataObjectType, null, dataObject, DataSourceOperation.Update);
        }
 
        IOrderedDictionary parameters = ExtractMethodParameters(updateMethod);
        ObjectDataSourceMethodEventArgs args = new ObjectDataSourceMethodEventArgs(parameters);
        this.OnUpdating(args);
 
        if (args.Cancel)
        {
            return 0;
        }
 
        object result = this.InvokeMethod(updateMethod);
 
        this.Owner.InvalidateCache();
        this.OnDataSourceViewChanged(EventArgs.Empty);
        return ExtractAffectedRows(result);
    }
 
    public virtual object AcquireDataObject(Type dataObjectType, IDictionary inputParameters)
    {
        return Activator.CreateInstance(dataObjectType);
    }
 
    // Oh, if only this was a virtual on ObjectDataSourceView!
    private object BuildDataObject(Type dataObjectType, IDictionary inputParameters)
    {
        object dataObject = AcquireDataObject(dataObjectType, inputParameters);
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(dataObjectType);
 
        foreach (DictionaryEntry entry in inputParameters)
        {
            string propertyName = (entry.Key == null) ? string.Empty : entry.Key.ToString();
            PropertyDescriptor descriptor = properties.Find(propertyName, true);
 
            if (descriptor == null)
            {
                throw new InvalidOperationException(ExposedSR.GetString(ExposedSR.DataObjectPropertyNotFound, new object[] { propertyName, this.Owner.ID }));
            }
 
            if (descriptor.IsReadOnly)
            {
                throw new InvalidOperationException(ExposedSR.GetString(ExposedSR.DataObjectPropertyReadOnly, new object[] { propertyName, this.Owner.ID }));
            }
 
            object propertyValue = BuildObjectValue(entry.Value, descriptor.PropertyType, propertyName);
            descriptor.SetValue(dataObject, propertyValue);
        }
 
        return dataObject;
    }
 
    #region Proxies for private methods
    private AcquiringObjectDataSource Owner
    {
        get { return (AcquiringObjectDataSource)s_Owner.GetValue(this); }
    }
 
    private Type GetType(string typeName)
    {
        return (Type)s_GetType.Invoke(this, new object[] { typeName });
    }
 
    private Type TryGetDataObjectType()
    {
        return (Type)s_TryGetDataObjectType.Invoke(this, null);
    }
 
    private static void MergeDictionaries(ParameterCollection reference, IDictionary source, IDictionary destination)
    {
        s_MergeDictionaries.Invoke(null, new object[] { reference, source, destination });
    }
 
    private static object BuildObjectValue(object value, Type destinationType, string paramName)
    {
        return s_BuildObjectValue.Invoke(null, new object[] { value, destinationType, paramName });
    }
 
    private object GetResolvedMethodData(Type type, string methodName, Type dataObjectType
                                         , object oldDataObject, object newDataObject, DataSourceOperation operation)
    {
        return s_GetResolvedMethodData.Invoke(this, new object[] { type, methodName, dataObjectType, oldDataObject, newDataObject, operation });
    }
 
    private IOrderedDictionary ExtractMethodParameters(object method)
    {
        return (IOrderedDictionary)s_Parameters.GetValue(method);
    }
 
    private object InvokeMethod(object method)
    {
        return s_InvokeMethod.Invoke(this, new object[] { method });
    }
 
    private int ExtractAffectedRows(object result)
    {
        return (int)s_AffectedRows.GetValue(result);
    }
    #endregion
}
Once again, I'm doing the reflection work once in a static constructor. In my next (and final?) post on this mess, I'll show you how my DynamicCall stuff makes this code look much nicer and makes it tons type-safer (hmmm... is that something I want to have forever Google-able?). There are a couple of notes.
  1. In all but the insert case, I have a list of keys provided by my caller in a seperate collection. It would be nice to be able to pass that along to the AcquireDataObject method, but it turns out that this isn't necessarily possible or sufficient. Since the ObjectDataSource forwards any explicitly-defined parameters through to the ObjectDataSourceView the key collection given to the update and delete methods may or may not contain the actual key. Rather, the key could be coming from the InsertParameters, DeleteParameters or UpdateParameters collections (which get merged-in in the respective mutation methods).
  2. It would be nice for the AcquireDataObject method to be told why we were trying to acquire the object (e.g. are we doing an insert, update or delete). I didn't make that change in this verison simply to make it easier the correlation to the Microsoft verison, but in the final post, I've added that argument. This allows calling the correct factory method or any other operation-specific behavior.
  3. The calls to this.Owner.InvalidateCache() replaces the bit of class-envy in the original Microsoft version that I talked about in part one.

Recycling messages (or how not to be responsible for translating error messages).

In the continuing saga of my replacement for ObjectDataSource and ObjectDataSourceView, I have had to reimplement some of the methods in those classes. This is done, of course, by using Reflector to see what is currently there and tweaking appropriately. In the process of replacing a method, you probably want to check the same argument conditions and throw the same exceptions as the originals in System.Web.dll. If you can find a way to expose the existing messages and reuse them, you get the added benefit of not having to worry about translation/localization of those error messages. To that end, here's my ExposedSR class that hoists the messages I need from System.Web.dll

internal static class ExposedSR
{
    private static readonly ResourceManager s_Resources = new ResourceManager("System.Web", typeof(ObjectDataSourceView).Assembly);
 
    internal static string GetString(string name)
    {
        return s_Resources.GetString(name, null);
    }
 
    internal static string GetString(string name, params object[] args)
    {
        string text = s_Resources.GetString(name, null);
 
        if (args == null || args.Length == 0)
        {
            return text;
        }
 
        // clip all the string arguments to less than 1K length
        for (int index = 0; index < args.Length; index++)
        {
            string argString = args[index] as string;
 
            if (argString != null && argString.Length > 0x400)
            {
                args[index] = argString.Substring(0, 0x3fd) + "...";
            }
        }
 
        return string.Format(CultureInfo.CurrentCulture, text, args);
    }
 
    internal static readonly string Pessimistic = "ObjectDataSourceView_Pessimistic";
    internal static readonly string InsertNotSupported = "ObjectDataSourceView_InsertNotSupported";
    internal static readonly string UpdateNotSupported = "ObjectDataSourceView_UpdateNotSupported";
    internal static readonly string DeleteNotSupported = "ObjectDataSourceView_DeleteNotSupported";
    internal static readonly string InsertRequiresValues = "ObjectDataSourceView_InsertRequiresValues";
    internal static readonly string Update = "DataSourceView_update";
    internal static readonly string Delete = "DataSourceView_delete";
    internal static readonly string InvalidViewName = "DataSource_InvalidViewName";
    internal static readonly string DataObjectPropertyNotFound = "ObjectDataSourceView_DataObjectPropertyNotFound";
    internal static readonly string DataObjectPropertyReadOnly = "ObjectDataSourceView_DataObjectPropertyReadOnly";
}

The trick here is to create a class that mirrors the normal SR class that is automatically generated by the resources compiler, but coopt it to use the assembly containing the resources you really want. Then readonly strings are there to ease readability when using ExposedSR.

Friday, July 21, 2006

Browser-specific CSS the clearest way possible

How would you like to have all your browser-specific adjustments be trivial to apply? What if you could have a <style> block like this?

<style type="text/css">
.ie .example {
  background-color: yellow
}
.gecko .example {
  background-color: gray
}
.opera .example {
  background-color: green
}
.konqueror .example {
  background-color: blue
}
.safari .example {
  background-color: black
}
.example {
  width: 100px;
  height: 100px;
}
</style>
Now all you need is some JavaScript to apply the correct top-level CSS class to the <html> element. We'll here you go, the CSS Browser Selector from Rafael Lima. Awesome simplicity!

Thursday, July 20, 2006

Enough with the whining, just fix ObjectDataSource already...

What's the deal?

If you have ever tried to use ObjectDataSource with any O/R Mapper or just business entities that have "persistent state", you no-doubt know the issues I've whined about. It really boils down, at the simplest level to the absence of "acquire" semantics for the instance of the DataObjectType specified. In other words, you can just get the "old" instance of the business entity object from your persistence-store / cache / web-service / etc. and then let GridView / DetailsView / FormView update just the properties it has exposed on the user-interface. This is impossible because ObjectDataSourceView simply calls Activator.CreateInstance to create an object ex-nilo. If there was a method you could override on ObjectDataSourceView to acquire the object before all the properties are fiddled-with, then all would be good, but there isn't. I'm going to spend the next few posts talking about how to accomplish that goal. If I'm lucky, this will shame Microsoft into fixing this for the next framework release.

The first hurdle is stupidity design issues.

First, we have to deal with the fact that some things are simply not as they should be in the ObjectDataSource class itself. Firstly, there are a few key fields, properties and types that are private, notably the _view field, the Cache property, and the Enabled property of the SqlDataSourceCache that the Cache stores (not to mention the fact that the SqlDataSourceCache is also private). Secondly, there's a bit of extreme silliness in the implementation of the ,code>GetView call-chain. Like all good DataSourceControls, ODS has an override for the GetView(string viewName) virtual method. This method's body is pretty simple, just verifying you are being a good citizen and passing only the view names that ODS knows about, namely null, String.Empty or "DefaultView", then it just calls the GetView() method to create the actual ObjectDataSourceView instance.

protected override DataSourceView GetView(string viewName)
{
      if ((viewName == null) || ((viewName.Length != 0) && !string.Equals(viewName, "DefaultView", StringComparison.OrdinalIgnoreCase)))
      {
            throw new ArgumentException(SR.GetString("DataSource_InvalidViewName", new object[] { this.ID, "DefaultView" }), "viewName");
      }
      return this.GetView();
}
The GetView() method just does a lazy instantiation against the private ObjectDataSourceView _view field.
private ObjectDataSourceView GetView()
{
      if (this._view == null)
      {
            this._view = new ObjectDataSourceView(this, "DefaultView", this.Context);
            if (base.IsTrackingViewState)
            {
                  ((IStateManager) this._view).TrackViewState();
            }
      }
      return this._view;
}
Here's where things really start to go wrong... the GetView() method is private and non-virtual, so you can't simply override it to return a subclass of ODSV that does what we want. Ah, but that's okay, we can override the GetView(string viewName) virtual method to do what we need, right? Nope, because nowhere in the code of ODS does that method get called. All the standard action methods (like Select(), Delete(), Insert() and Update()) directly call the non-replacable GetView() private method and then directly delegate the action. Ick. Okay, no problem, we just override the action methods, right? Nope, again... they are also non-virtual. Also, there are tons of properties on ODS that do the same direct call to GetView() and delegate the call down, this is especially bad for things like EnablePaging and similar properties, since they get called while we're still constructing the ODS in the cool new declarative coding model of ASP.Net 2.0.

What this basically means is that we're going to have to code up a derived class replacement for ODS and then poke in our replacement for the _view in the private bits. Without further adieu, let me introduce AcquiringObjectDataSource (I've eliminated all the XML documentation, comments and attributes for this post):

public class AcquiringObjectDataSource : ObjectDataSource
{
    const BindingFlags NeedlesslyPrivate = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
 
    static MethodInfo s_InvalidateCacheEntry;
    static MethodInfo s_GetCache;
    static MethodInfo s_GetCacheEnabled;
    static FieldInfo s_View;
 
    static AcquiringObjectDataSource()
    {
        s_InvalidateCacheEntry = typeof(ObjectDataSource).GetMethod("InvalidateCacheEntry", NeedlesslyPrivate, null, new Type[0], null);
        PropertyInfo cache = typeof(ObjectDataSource).GetProperty("Cache", NeedlesslyPrivate);
        s_GetCache = cache.GetGetMethod(true);
        PropertyInfo cacheEnabled = cache.PropertyType.GetProperty("Enabled", NeedlesslyPrivate);
        s_GetCacheEnabled = cacheEnabled.GetGetMethod(true);
        s_View = typeof(ObjectDataSource).GetField("_view", NeedlesslyPrivate);
    }
 
    public AcquiringObjectDataSource()
    {
        // force creation!
        this.GetView();
    }
 
    public AcquiringObjectDataSource(string typeName, string selectMethod)
        : base(typeName, selectMethod)
    {
        // force creation!
        this.GetView();
    }
 
    protected virtual ObjectDataSourceView GetView()
    {
        ObjectDataSourceView view = (ObjectDataSourceView)s_View.GetValue(this);
 
        if (view == null)
        {
            view = new AcquiringObjectDataSourceView(this, "DefaultView", this.Context);
            s_View.SetValue(this, view);
 
            if (base.IsTrackingViewState)
            {
                ((IStateManager)view).TrackViewState();
            }
        }
 
        return view;
    }
 
    protected override DataSourceView GetView(string viewName)
    {
        if (viewName == null || (viewName.Length != 0 && !string.Equals(viewName, "DefaultView", StringComparison.OrdinalIgnoreCase)))
        {
            throw new ArgumentException(ExposedSR.GetString(ExposedSR.InvalidViewName, new object[] { this.ID, "DefaultView" }), "viewName");
        }
 
        return this.GetView();
    }
 
    protected void InvalidateCache()
    {
        object cache = s_GetCache.Invoke(this, null);
        object cacheEnabled = s_GetCacheEnabled.Invoke(cache, null);
 
        if ((bool)cacheEnabled)
        {
            s_InvalidateCacheEntry.Invoke(this, null);
        }
    }
}
Some notes are in order to explain that code:
  1. We force creation of the view during our constructor to insure our GetView() gets a chance to create an instance of our AcquiringObjectDataSourceView before something in ODS causes one to be created. This isn't as lazy as possible, but it's safe.
  2. We have static constructor to do the one-time Reflection to get all the private bits we want to play with. In a later post, I'll show you how to use the DynamicCall stuff in my Utlities library to handle this is a much type-safer and faster way.
  3. We override GetView(string viewName) merely to insure that if someone does call it, we will do the right thing and call our GetView() and not the one in ODS
  4. That last method is there to cure a little class-envy on the part of ODSV, which makes a few calls against the internal SqlDataSourceCache Cache property. They typically look like this:
    if (this._owner.Cache.Enabled)
    {
       this._owner.InvalidateCacheEntry();
    }
  5. The ExposedSR class is just something letting me reuse the localized exception messages embedded in the System.Web.dll assembly, we'll see it in part 3 part 2.

Next time, right here...

Next time In part 3, I'll talk about the issues in ObjectDataSourceView and present the replacement class AcquiringObjectDataSourceView