I've been working with the WCF RIA Services framework in Silverlight 4 now for a few months. The theory behind it is very interesting, and I can certainly see how it gives that feeling of a true 'RIA' framework. In general, so long as you 'stick to the path', everything just works, which is nice for once. There certainly are problems, and I won't go into them all here. I certainly don't have time to detail the many and varied complaints about the metadata classes that make validation work, nor do I have time to wonder why the generated domain service itself is not marked as 'partial'. What I would like to do is add my voice to the hundreds of others who are asking, 'Why can't regeneration work?'. Of course, just doing that is boring, so I'll make up a badly written narrative of a lonely developer working his way through his first RIA project.
DISCLAIMER: No code monkeys were harmed in the creation of this story, the story and any project details are entirely fictional and do not represent a real world project in any way, shape or form.
When George first started this RIA project, he happily built his data model and added that first domain service to the project. Everything generated beautifully, he built out the first pass at the application and all was right with the world. George wondered about these metadata classes, but they seemed usable, if somewhat weird and constraining. So he went about the business of adding validation rules to the metadata classes and marveled at the ease in which those error messages appeared on the UI, almost as if by magic, as he had done nothing but setup appropriate binding [remembering to include those NotifyOnValidationError and ValidatesOnExceptions attributes].
The customer was happy, George was happy, and then the first official review arrived. Turns out George's data model was off, just a bit, in just a few places. Oh, and the customer needed these few things added over there. There was also this entity that marketing never mentioned, but now that they see things, they'll definitely need that. "No problem," George says, "that's what these reviews are for, and we're happy to make those adjustments. Our agile process allows us to do that with minimal longterm project impact." George leaves the meeting refreshed and ready to tackle the next few weeks.
George returns to his desk ready to go. The database changes are knocked out without a hiccup, and the update of the model from the database goes similarly smoothly. There's a few weird things, and he forgets to delete a couple fields that moved around, but overall no major headaches. Everything is looking good, the Silverlight client app is updated to accommodate the changed fields and everything is running smoothly... until he goes to add a child grid to maintain that new entity that was discovered.
The Story Falls Apart
"Of course", he thinks, "I've got to make sure I've generated the necessary methods on my service to handle the new entity. What a silly thing to forget, no problem, the first time was a breeze, I'll just update things and get back to work." Then George spends the next 30 minutes searching through Visual Studio to find the button/configuration file/etc that needs to be updated to trigger regeneration. After 15 minutes, he's convinced he won't find anything, but he can't believe this particular use case wasn't handled out of the box. After 30 minutes he gives up and then tries to decide how in the world he's going to maintain all the customizations that he has done while still generating the new code. He eventually decides to just hand-code any updates that are necessary, using a couple of the other classes as templates. The work is slow and tedious, but after a few attempts and some false starts, he manages to get everything working again. In total, near half a day spent making updates that should have taken less than 1 hour of time. He's back on track and moving forward, but he shutters to think about the next time he has to make changes, something virtually guaranteed by the agile processes he has used in the past so successfully...
So, what can we learn from George's story here? I think the first take home is that the RIA services 'regeneration' story is not a good one. Given the amount of code and logic behind the original generation process, it seems pretty silly to have left out any hooks to enable this process natively. Visual Studio provides loads of extension points that automatically rebuild your services from a standard, configurable 'source' [the EF4 designer/code generation process is one such example]. The RIA Services 'Domain Service' wizard already requires that your solution compile, so why doesn't it produce something akin to the edmx files that EF4 uses which are then used to generate your services at compile time?
I, obviously, cannot answer that question, and I truly hope that Microsoft is working hard to improve the end user experience of RIA services to address some of these problems. They have a site setup to handle 'feature requests' for RIA Services, and there are a few potential candidates that would make our lives easier:
And this isn't an exhaustive list, just a few that I picked out from the top vote getters. This is a problem that needs to be solved, it's a giant pain to deal with now, but I'll walk through some steps that we've taken on projects to help alleviate the pain.
Alleviating the Pain
There are a few concrete steps that you can take to help alleviate the pain caused by the lack of regeneration capabilities. Whether they end up being more or less work than hand coding will largely depend on the scope of the changes you need to make. We use a combination of these methods and hand-coding to get the job done now. There is anecdotal evidence that the methods below don't always work, and sometimes a restart of visual studio, or a full check-in/check-out cycle may be needed to trigger all the right things to happen.
Isolate custom metadata into a sub-directory, and use a 'single-class per file' approach
When regenerating, any existing metadata classes will NOT be regenerated, so putting them in their own directory allows easy 'Include/Exclude from Project' to force regeneration of all metadata. Putting classes in their own file allows easy 'Include/Exclude' of a particular file to force metadata regeneration. We usually use '<EntityName>.metadata.cs'. Don't forget to put these classes in the 'Model' namespace, regardless of their location, so they match up with the EF4 generated classes.
Isolate 'extensions' to entities from metadata
When regenerating, it is necessary for your application to successfully compile. While metadata updates do not generally result in compilation errors, extension updates generally do result in compilation errors. So isolating these in their own file, which can be retained while also temporarily excluding metadata allows the regeneration process to proceed easily. We usually use '<Entity>.extensions.cs'. Don't forget to put these classes in the 'Model' namespace, regardless of their location, so they match up with the EF4 generated classes.
Isolate service extensions into their own file
When building RIA services, you invariably will want to add custom service methods to facilitate custom searches, loads, invokes, etc. Even though the main DomainService derived class does NOT by default generate with the 'partial' modifier, it is simple to add and allows much greater 'regenability' by streamlining the process of regenerating the bulk of the service code. We usually use '<ServiceName>DomainService.extensions.cs'.
'Regenerate' Code using existing Wizard
You can use the existing wizard to regenerate code, you just need to act as if you're creating a new service. The steps we use are:
- Exclude any metadata classes that need to be regenerated [or the whole folder to get them all]
- Clean and Rebuild the solution. This step is crucial, as it ensures that regeneration will function appropriately.
- Add a 'new' Domain Service to the project with the same name and a '.temp.cs' extension
- In the resulting dialog, clear the '.temp' from the service name and select the entities you would like to include in the service [yes, it's a pain to reselect them all every time].
- Copy the resulting output from the <ServiceName>DomainService.temp.cs file into the <ServiceName>DomainService.cs file, remembering to re-add the 'partial' modifier to the service class.
- Merge the resulting output from the <ServiceName>DomainService.temp.metadata.cs file into the classes in the 'Metadata' folder.
- Delete the two new .temp.cs files that were generated.
- 'Include' all files excluded in Step #1.
- Clean and rebuild.
The above steps accomplish a few things. They allow us to retain any custom changes we've made. They allow us to get the 'latest' version of the metadata and potentially save some typing in exchange for gratuitous cut-and-paste. They allow source control branching/merging to work as desired, because we aren't deleting/re-adding files to the project that we care about [i.e. the .temp.cs files are never checked in, file changes are content based]. They keep the various concerns [core entities/service, metadata, extensions] separate and 'more maintainable' [to some folks].
As mentioned at the start, this story needs to be improved. I'm hopefully that future versions of RIA services will improve on these areas, but in the mean time, we'll have to continue to find work arounds and 'best practices' that allow us to be productive using these tools and the agile processes that normally run our projects. If you have additional techniques that you've used, things that I've missed, or just want to add your voice to the issue, please leave your comments below.