Ayende @ Rahien

Refunds available at head office

Effectus: Building UI based on conventions

I waited for a while after my article was posted, to see if anyone caught on to some of the things that I did in the sample code that weren’t related to NHibernate usage. It seems that no one caught on to that, so I’ll try pointing them out explicitly.

The basic format of a feature in Effectus is this:

image

Each feature have a Presenter (business logic for the feature), a View Model, which is directly bound to the View and is the main way that Presenter has to communicate with the View. The main responsibility of the Model is to be bound to the UI, and allow the presenter to update it, but it is not quite enough.

The UI also need to raise events and handle user input. A common example is handling button clicks. You can’t really map them into Model behavior, at least not easily and in a way that make sense to the developers. Instead, I defined a set of conventions. As you can see in the picture, based on the name of the element, we match is with the appropriate execute method and the way to decide if the command can execute. Under the cover, we generate the appropriate ICommand instance, bound to the Presenter behavior.

image

If you look at the code, you can see that the conventions extend even to handling the selected item changed in a list box, including passing the newly selected instance back to the presenter. Those conventions are project specific, based on what the project need, and they result in a code base that is very easy to read and follow. Moreover, they result in a codebase that is relatively free from infrastructure concerns.

I am going to have a few more posts about Effectus, can you figure out what else I put in that code base?

Comments

Omer Mor
12/19/2009 11:09 AM by
Omer Mor

A little thing called "love" ?

seriously I have no idea, but I'm sure the following comments will unveil it :-)

Ayende Rahien
12/19/2009 11:12 AM by
Ayende Rahien

Omer,

Read the code, and look at the future posts :-)

firefly
12/19/2009 11:52 AM by
firefly

What is a "Fact"? What design pattern does that follow?

Ayende Rahien
12/19/2009 11:55 AM by
Ayende Rahien

Read the code, and wait for the post to show up

firefly
12/19/2009 11:55 AM by
firefly

Also the sql script throw some error in SQL Management studio.

INSERT INTO [ToDo].[dbo].[ToDoActions]

       ([Version],

        [Title],

        [Content],

        [Status],

        [CreatedAt],

        [CompleteBy])

VALUES (1,

        'Write App',

        'Should really write the application',

        1,

        Getdate(),

        Getdate() + 3,)  <--- the extra comma in all the insert cause the error. Remove this will fix it.
Mihai Lazar
12/19/2009 12:09 PM by
Mihai Lazar

I don't know why you thought nobody noticed what you put in the code. The command publishing I was looking for was found in your code ;)

Ayende Rahien
12/19/2009 12:16 PM by
Ayende Rahien

Mihai,

I didn't get any response about that

firefly
12/19/2009 12:23 PM by
firefly

Thanks alwin, fact is a pretty generic word... so it's kinda hard to search for it :)

Dennis
12/19/2009 03:49 PM by
Dennis

Reinventing CSLA?

Eugene
12/19/2009 04:07 PM by
Eugene

A possibly offtopic question regarding facts. I post it here since unfortunately the comments for the post linked by alwin are closed.

public Fact(INotifyPropertyChanged observable, Func <bool predicate) ...

Why do you require an observable if you could decompile the predicate, hunt for INPCs mentioned there and listen to those? That would work as intended for a good 90-95% of all cases.

In the cases when a simple heuristics works for us (i.e. it detects all INPCs we need and doesn't detect INPCs that are irrelevant) we can get rid of Facts at all, e.g. change the original sample:

public Fact CanMovePrev { get { return new Fact(CurrentPage, () => CurrentPage > 0); } }

to a much more readable analogue:

public bool CanMovePrev { get { return CurrentPage > 0 } }

Daniel AUger
12/19/2009 04:13 PM by
Daniel AUger

Ayende - When I read the article the thought did cross my mind that the non-nhibernate aspects of Effectus were extremely interesting. Most "sample applications" have a throwaway / bland architecture and infrastructure. The Effectus architecture and infrastructure really grabbed my attention. I'm very happy you are going to elaborate on these aspects via your blog.

Eugene
12/19/2009 04:16 PM by
Eugene

Also wanted to add that the word "decompile" I used doesn't meant fully-fledged decompilation like the ones Reflector does.

Imo we can be fine with just checking all ldflds and calls to property getters - whether they return INPCs. To do that one needs to find a way to convert byte[] method bodies returned by reflection to a stream of ops. For that purpose one can use Cecil, CCI or something hand-made. E.g. for a similar purpose I've developed my own IL reader: xenogears.googlecode.com/.../Parse/

David Perfors
12/19/2009 04:44 PM by
David Perfors

Ayende - The thing that I found weird was that you decoupled the features so much that you use the name of the namespace (or part of it) as a string to fire up the presenter. It is not something I would do (since I don't like strings in code..), I am looking forward to the post about this...

The way you use for wire the events is very nice :)

ivos
12/19/2009 04:59 PM by
ivos

Pretty interesting, actually. I used to work with MVP for my web applications and I don't reallt like MVVM (It's not bad, but I don't like it).

Features that I would like to see how it's implemented:

  • Validation: I would like to have the validation in the model side.

  • Transaction management: maybe an aspect in the On.... methods?

  • Tell the view to hide/show controls (change the visual state): The properties would be bound to presenter properties?

I didn't downloaded the code, just navigated it a little, so maybe there's something already defined that I missed.

Ayende Rahien
12/19/2009 05:02 PM by
Ayende Rahien

Eugene,

Because that would be:

a) a lot of hard work

b) trigger the too much magic part

c) would invalidate scenarios such as composite observables

Eugene
12/19/2009 05:26 PM by
Eugene

Ayende,

a) and c) need to be done only once, and then can be used across all projects that use facts - tho surely do as it feels good for you to do.

What really got me interested is b). How do you detect the "too much magic"? For me things that don't leak their internals are always ok, regardless of their complexity, given that they do simplify routine tasks. What's your opinion on this?

Mark Nijhof
12/19/2009 06:31 PM by
Mark Nijhof

I really like Conventional based stuff a lot. Something that I did in my CQRS example application is using Win Forms and MVP where I conventionally hook-up the view events with presenter event handlers. It is using reflection which doesn't get cached but honestly on a window start this isn't really a big concern. And if there is an event that doesn't get handled I throw an exception.

The base presenter can be found here: github.com/.../IPresenter.cs

-Mark

Alexandre Trigueros
12/19/2009 06:47 PM by
Alexandre Trigueros

My first post here to send you a big thank you !

I didn't have much opportunities to use WPF.

Your sample is a gold mine.

Even if i'm not really fond of the autowiring event based on name convention.

And also, why did you do an Observable class instead of using the databindingFactory to generate INotifyPropertyChanged objects ?

Anyway, thanks again for the sample.

Mikael Syska
12/19/2009 08:13 PM by
Mikael Syska

Great stuff, always enjoy reading your posts.

c.sokun
12/19/2009 11:04 PM by
c.sokun

Love to see DataBindingFactory extend to support 1-1, 1-M & M-M oh would that require programmer to deal with it themself? Would be cool is I could have the same experience as [ARDataBind] in MR gave :)

Ayende Rahien
12/20/2009 10:32 AM by
Ayende Rahien

Eugene,

a & c may be only needed once, but that is still complex, and I don't see any huge benefit that it gives in return.

As for too much magic, that is simple formula, how much time are you going to spend debugging a particular problem?

Ayende Rahien
12/20/2009 10:34 AM by
Ayende Rahien

Alexandre,

data binding factory is for entities, not for the type of stuff that I used Obserable for. It is simpler to write the INPC for that

Ayende Rahien
12/20/2009 10:34 AM by
Ayende Rahien

Sokun,

You have the code, write the extension

Eugene
12/20/2009 10:58 AM by
Eugene

Ayende,

Thank you.

Josh
12/20/2009 03:24 PM by
Josh

This is how Caliburn does it, I assume it was your contribution?

firefly
12/20/2009 04:47 PM by
firefly

After some digging I must say that I really like the code base. I love convention over configuration. With some minor refactoring I was able to incorporate most of it into my code base. Very neat :)

This come just in time as I am working on my first WPF application and I started to get a little sick of the wiring.

FallenGameR
12/20/2009 05:12 PM by
FallenGameR

Nice architecture =) I wonder if I need MS Prism Commands now.

Ayende, how do you think - will it be easy to add validation to model properties?

firefly
12/21/2009 12:15 AM by
firefly

So is this stuff going to end up in Caliburn v2? :) Somehow I get the feeling that it will be.

It's actually cleaner than the way Caliburn is doing it currently. It's cleaner for two reason. First Caliburn put the INotifyPropertyChanged on the Model, this put it directly on the property.

Putting it on directly the model require that the user have to raise seperate notification. For example

public string LastName

{

get { return _lastName; }

set

{

_lastName = value;

NotifyOfPropertyChange("LastName");

NotifyOfPropertyChange("CanSave");

}

}

I think that is a little ugly. If there is more commands that tied to LastName the situation get hairy real fast...

P
12/22/2009 12:15 AM by
P

Slightly off the topic of this post, but the naming convention in the per-scenario is something I struggle with :)

Doesnt this kill the "Go to type" (Ctrl + N for resharper bindings) feature in resharper? For a large code base, this is a must IMO.

I completely agree with a folder (namespace) per scenario, but I typically add the scenario name to Presenter/ViewModel/View just to support resharper navigation. Its redundant, but I would buy a resharper license just for the said feature alone.

P
12/22/2009 12:33 AM by
P

Oh and...

[Quote]Each feature have a Presenter (business logic for the feature), a View Model, which is directly bound to the View and is the main way that Presenter has to communicate with the View. The main responsibility of the Model is to be bound to the UI, and allow the presenter to update it, but it is not quite enough.[/Quote]

I cannot agree more. MVVM forces people to shove a bunch of logic into something that should be strictly for databinding. Presenter-ViewModel-View FTW!

firefly
12/22/2009 01:07 AM by
firefly

Resharper should work fine, each in it own namespace.

Ayende Rahien
12/22/2009 07:08 AM by
Ayende Rahien

Josh,

Nope, that was parallel evolution

Ayende Rahien
12/22/2009 07:09 AM by
Ayende Rahien

FallenGameR,

It should be very easy, sure. Either directly in the property set or as an extra layer that will handle this in the DataBindingFactory.

It may take a bit of infrastructure on the UI side to handle validation errors correctly, but that wouldn't be too hard.

Ayende Rahien
12/22/2009 07:11 AM by
Ayende Rahien

P,

Yes, it would. The naming isn't mandatory, and I am not too attached to it.

Christopher Bennage
12/22/2009 03:16 PM by
Christopher Bennage

@firefly you can use this approach in Caliburn v1 RTW. It still needs to mature a bit, but it's the approach we took on our most recent project. Though we occasionally ran into areas where we had to use the old Caliburn style.

firefly
12/22/2009 10:13 PM by
firefly

@Christopher, thanks for clarifying that. I studied your example and I wasn't real clear of what convention to follow. Though isn't the new convention doing it right in the xaml?

P
12/23/2009 04:53 AM by
P

@firefly

Resharper will work fine calling everything presenter, but when you want to goto the "Inactive Customer Email Presenter" (for example), you can no longer jump to "ICEP" because you only called it "Presenter". You are basically back to manually navigating via solution explorer.

firefly
12/23/2009 05:12 AM by
firefly

@p You can type Presenter, it will list all of the presenter... along with their namespace. Not that it's optimal, just that it still work.

firefly
12/23/2009 05:15 AM by
firefly

@p, Oh I also want to say thanks, I didn't know about that feature until you mentioned it the other day. There is always something about Resharper to discover :). I've always used Ctrl Click to navigate

Matt Warren
01/13/2010 03:50 PM by
Matt Warren

Ayende, thanks so much for this code, it's just what I needed. I converted it to work with .NET 2.0 and Winforms for a project I'm working on.

lg
01/18/2010 04:24 PM by
lg

Hmmm.... I'm not a pro,, so I wonder,,, translations to other languages... it is possible or will break the binding?

//lg

Ayende Rahien
01/21/2010 07:45 AM by
Ayende Rahien

Ig,

No, it wouldn't. It just depend how you are handling the i18n

Comments have been closed on this topic.