The journeylism of @yreynhout

On CQRS, DDD(D), ES, …

Category Archives: UI

Trench Talk – Authorization

For a long time now, I’ve been dealing with the subject of authorization and its bigger brother, authentication. Not that dealing with these makes me an expert, mind you. But it did arouse my interest in the areas of RBAC (role based access control) and family (hierarchical RBAC, administrative RBAC, attribute based RBAC, …), RAD (resource access decision) from OMG fame, XACML (eXtensible Access Control Markup Language) from OASIS, and a few others I’ve lost track of over the years. Why do we embed authorization into software, though? I can understand the need in military and nuclear facilities, or when company secrets are involved. But beyond that it’s mainly a game of fear, risk, trust and workflow. In general, in end-user facing systems, there are two forces at play: one is the top-down, enterprisy, we-want-control-over-who-sees-or-does-what and the other is the this-is-my-stuff-and-i-say-who-gets-to-see-it-and-manipulate-it. It’s not uncommon to find both in one software system you’re working on. The reason is quite simple: administration overhead. Imagine controlling each and every employee’s role(s), exceptions to such role(s), role transitions over time, etc … and you’ve got 90,000 of ’em. While some organizations are – believe it or not – willing to pay money to their IT department (in a low-cost country, no doubt) to take care of this task, others have flattened their structure or adopted controlled delegation to overcome this. I guess, a lot depends on “the culture within”. Bottom line, to me, is that fine-grained, top-down control doesn’t scale very well – administratively speaking – and is entangled with topics of trust, risk, and – for those at the bottom – frustration. This is usually the point where we tend to see the “owner-centric” approach to authorization, where “owners” impose who gets to see or manipulate a particular resource or set of resources they own or co-own. If we’re lucky, that is.

Authorization models are fascinating. There have been many attempts at abstracting this domain and treating it as a “separate” problem. All in all we’ve been pretty successful at doing that. Think of all the products out there that incorporate the notion of storing and enforcing authorization “policies”. I’ve always been in environments where I couldn’t use off-the-shelf products for this particular problem. Mostly because it would have meant coupling to a particular technology and the inflexibility/inadequacy they brought with them. So, yes, in case you’re wondering, I’ve reinvented this wheel a couple of times. Why? Because, amongst other reasons, the data to base the access decision on was tightly coupled to the domain at hand. Depending on the required “freshness” of said data with regard to securing a particular resource you will want to design it one way or another.

What I like about Domain Driven Design is that it embraces the idea of entertaining multiple models to solve a particular problem. Of course, with choice comes responsibility, trying things out, failing, heck, even stupidity, but above all learning from those. Being focused and doing small, controlled experiments – verbally at the whiteboard or textual as in a code probe – may mitigate the risk of going down the wrong path. Such endeavors depend on the skill and experience of those involved, IMO. Yet by no means is success guaranteed. Such is life … how depressing. Still, let’s look at the bright side … of life, that is.

Make the implicit explicit

Let’s look at a small example … Imagine a piece of software that allowed one or more people to collaborate on an art design. Below I’ve coded up the use case of inviting somebody to collaborate with somebody else on a particular design.

public class InviteArtDesignCollaboratorHandler : Handles<InviteArtDesignCollaborator>
{
  //invite somebody to collaborate with you on a certain art design.
  public void Handle(InviteArtDesignCollaborator message)
  {
    var inviter = this.personRepository.Get(new PersonId(message.InviterId));
    var invitee = this.personRepository.Get(new PersonId(message.InviteeId));
    var artDesign = this.artDesignRepository.Get(new ArtDesignId(message.ArtDesignId));
    var invitation = artDesign.InviteCollaborator(inviter, invitee);
    this.invitationRepository.Add(invitation);
  }
}

If we zoom in on the authorization aspect of this use case, what do we see in the above code? Nothing, that’s right. Who can invite collaborators to an art design? The original creator of such a design IF and only IF he is still a participant of the collection the art design is part of. Who can be invited to an art design? Somebody who already participates in the collection the art design is part of. Obviously, I’m making this up as I go. Yet, these aren’t uncommon requirements to come across. Let’s see how this manifests itself intermingled with the above code.

public class InviteArtDesignCollaboratorHandler : Handles<InviteArtDesignCollaborator>
{
  //invite somebody to collaborate with you on a certain art design.
  public void Handle(InviteArtDesignCollaborator message)
  {
    var inviter = this.personRepository.Get(new PersonId(message.InviterId));
    var invitee = this.personRepository.Get(new PersonId(message.InviteeId));
    var artDesign = this.artDesignRepository.Get(new ArtDesignId(message.ArtDesignId));
    var collection = this.collectionRepository.Get(artDesign.CollectionId);
    if (!artDesign.WasOriginallyCreatedBy(inviter))
      throw new NotAuthorizedException("The inviter is not the original creator of the art design.");
    if (!collection.HasAsParticipant(inviter))
      throw new NotAuthorizedException("The inviter is not a participant of the collection the art design is part of.");
    if (!collection.HasAsParticipant(invitee))
      throw new NotAuthorizedException("The invitee is not a participant of the collection the art design is part of.");
    var invitation = artDesign.InviteCollaborator(inviter, invitee);
    this.invitationRepository.Add(invitation);
  }
}

What if we were to separate the authorization responsibility from the actual use case? Why would we do that? Does changing the rules about who can be invited and who can invite fundamentally change this use case? Aren’t the axis of change different for these two? If yes, then the next piece of code might make more sense. Mind you, it’s a matter of preference at this point. Requirements might push it more in one or the other direction as to “the code feeling natural”.

public class InviteArtDesignCollaboratorAuthorizer : Authorizes<InviteArtDesignCollaborator>
{
  //invite somebody to collaborate with you on a certain art design.
  public void Authorize(InviteArtDesignCollaborator message)
  {
    var inviter = this.personRepository.Get(new PersonId(message.InviterId));
    var invitee = this.personRepository.Get(new PersonId(message.InviteeId));
    var artDesign = this.artDesignRepository.Get(new ArtDesignId(message.ArtDesignId));
    var collection = this.collectionRepository.Get(artDesign.CollectionId);
    if (!artDesign.WasOriginallyCreatedBy(inviter))
      throw new NotAuthorizedException("The inviter is not the original creator of the art design.");
    if (!collection.HasAsParticipant(inviter))
      throw new NotAuthorizedException("The inviter is not a participant of the collection the art design is part of.");
    if (!collection.HasAsParticipant(invitee))
      throw new NotAuthorizedException("The invitee is not a participant of the collection the art design is part of.");
  }
}

public class InviteArtDesignCollaboratorHandler : Handles<InviteArtDesignCollaborator>
{
  //invite somebody to collaborate with you on a certain art design.
  public void Handle(InviteArtDesignCollaborator message)
  {
    var inviter = this.personRepository.Get(new PersonId(message.InviterId));
    var invitee = this.personRepository.Get(new PersonId(message.InviteeId));
    var artDesign = this.artDesignRepository.Get(new ArtDesignId(message.ArtDesignId));
    var invitation = artDesign.InviteCollaborator(inviter, invitee);
    this.invitationRepository.Add(invitation);
  }
}

We can now separate the authorization test specifications – who can perform the use case under what circumstances – from the actual use case specifications – what is the side effect of the use case, when can it happen/can’t it happen depending on current state. Is doing all this desirable? I guess it depends on what you are getting out of it.
Fundamentally, those query methods (“WasOriginallyCreatedBy”, “HasAsParticipant”) are using projected and provided state (input) to determine what their boolean return value should be. If we could live with relaxed consistency in this regard, we could even project these values “at another point in time” and use those instead of these query methods. Heck, we’d be able to ditch these query methods from our model altogether. But again, it largely depends on what kind of consistency you are expecting vis-à-vis the aggregate you are affecting. It’s also debatable whether we are dealing with a different model or not. After distillation, would these query methods still be there? Why? Are they essential or circumstantial? These aren’t questions a blog post can answer, I’m afraid. They are highly contextual.

Keep it simple when appropriate

In scenarios with simpler requirements we could choose an approach reminiscent of aspect oriented programming or pipes and filters. Below are a couple of examples. The assumption is that some piece of code will inspect the handler’s Handle method, look for the Authorization attribute and instantiate either a decorating handler or a pipeline component to represent the authorization based on the attribute’s properties.

public class StartSeasonPortfolioHandler : Handles<StartSeasonPortfolio>
{
  //start a new season portfolio for a subsidiary.
  [Authorize(Role="SubsidiaryAdministrators")]
  public void Handle(StartSeasonPortfolio message)
  {
    var subsidiaryId = new SubsidiaryId(message.SubsidiaryId);
    var season = Season.From(subsidiary, message.Season);
    if (this.portfolioRepository.HasPortfolioForSeason(subsidiaryId, season)) 
      throw new SeasonPortfolioAlreadyStartedException("A portfolio was already started for the specified season of the subsidiary.");
    var subsidiary = this.subsidiaryRepository(subsidiaryId);
    var portfolio = subsidiary.StartPortfolio(new PortfolioId(message.PortfolioId), new PortfolioName(message.Name), season);
    this.portfolioRepository.Add(porfolio);
  }
}

This example codifies the caller must be member of the role ‘SubsidiaryAdministrators’. Interestingly enough, you can’t really tell who “the caller” is from the above code, now can you? This was less obvious in the previous art design example. The assumption there was that the “inviter” was “the caller“. How do you know that for sure, though? It’s just data I could have easily spoofed. Alas, authentication – proving the caller’s identity – and message integrity – did somebody tamper with this message – are beyond the scope of what I want to discuss here (*). For now, let’s assume we know who the ambient caller is.

public class StartSeasonPortfolioHandler : Handles<StartSeasonPortfolio>
{
  //start a new season portfolio for a subsidiary.
  [Authorize(Permission="CanStartSeasonPortfolio")]
  public void Handle(StartSeasonPortfolio message)
  {
    //ommitted for brevity - same as above
  }
}

When dealing with fixed or hard coded roles becomes cumbersome administratively speaking, the next thing you’ll see is this shift in focus towards permission to perform a certain operation. It’s obviously easier, since I could be member of multiple, dynamic roles that have the CanStartSeasonPortfolio access decision set to allowed. Yet, what if multiple roles had conflicting access decisions? You’d need a strategy to resolve such issues. In finance, accounting, health care and military environments you may even encounter separation of duty.

public class StartSeasonPortfolioHandler : Handles<StartSeasonPortfolio>
{
  //start a new season portfolio for a subsidiary.
  [Authorize]
  public void Handle(StartSeasonPortfolio message)
  {
    //ommitted for brevity - same as above
  }
}

Command messages could prove to be natural authorization boundaries. In such case, the name of the message translates to a particular permission, being a very conventional way of modeling authorization. If only it was always this simple 🙂

One thing that is very different from the art design example is that the notion of authorization wasn’t very coupled to the model we were dealing with. It felt somewhat more natural to tuck it away behind an attribute. There was no urge to consider it part of the model that dealt with the use case itself. Why is that? I think because no decisions were made based on state that was part of the model.

(*) If you’ve been infatuated with OAUTH, OpenID, Kerberos, PKI, etc. in the past, I doubt I could bring anything new to the table.

Not just for writing

Upon till now we’ve been very focused on preventing callers from invoking behavior they are not allowed to. However formidable that may be, I often find that’s only half of the story. When dealing with users or automated services, we want to guide them towards the happy path, no? We want to tell them: to the best of my knowledge, at the point in time you’re asking me, these are the state transitions you are allowed to perform. That’s when it hit me. The same authorization decisions you’re performing on the write side are also required on the read side, albeit in a form that could be very different. Obviously, we’re dealing with more latency here. As soon as you’ve computed or queried the access decision, it could be modified behind your back. It may have to travel across a network, it may have to manifest itself in a user interface (e.g. enable or disable a button/link) or await a user’s decision, it may manifest itself in an automated service choosing an alternate path through its logic. It’s a hint/clue, at best. Yet, often end users are quite content with these decisions.
That said, to me the key differentiator is the fact that permissions are not limited to state changing behavior. Whether or not you are allowed to view a particular piece of information is something that usually only manifests itself on the read side. Also note that the read side is often less harsh. It doesn’t throw an exception at you, rather it filters or hides information from you. Often I find that you’ll have a mixture of view and behavior permissions, especially on the read side.
Below you’ll find a very technology specific example of how that might manifest itself in a web api.

[RoutePrefix("portfolios")]
public class PortfolioResourceController : ApiController
{
  [Route("{id}")]
  public IHttpActionResult Get(string id)
  {
    var identity = new ClaimsIdentity(RequestContext.Principal.Identity);
    return this.portfolioQueries.
      ById(id).
      Select(_ => Ok(_.CompleteAuthorization(identity, this.Url))).
      DefaultIfEmpty(NotFound()).
      Single();
  }
}

public class PortfolioResource 
{
  public PortfolioResource CompleteAuthorization(ClaimsIdentity identity, UrlHelper helper)
  {
    var links = new List<Link>();
    //Note: CanEdit|Delete|ViewPortfolioItems are extension methods on ClaimsIdentity that, internally, 
    //      use claims to answer the authorization requests in a similar way as how the
    //      write side would ask them.
    if(identity.CanEditPortfolio(Id)) 
      links.Add(new Link { Rel = "edit", Href = helper.GetLink<PortfolioResourceController>(_ => _.Put(Id)) });
    if(identity.CanDeletePortfolio(Id)) 
      links.Add(new Link { Rel = "delete", Href = helper.GetLink<PortfolioResourceController>(_ => _.Delete(Id)) });
    if(identity.CanViewPortfolioItems(Id)) 
      links.Add(new Link { Rel = "items", Href = helper.GetLink<PortfolioResourceController>(_ => _.GetItems(Id)) });

    return new PortfolioResource(Id, Name, Season, SubsidiaryId, links.ToArray());
  }
}

What’s important to realize, is that we’ll probably start looking for similarity and deduplication (dare I say DRY) of the same logic that happens to be invoked on the write and read side, and trying to find a common home for them. Over the past months I saw an entire model emerge to deal with this and something as difficult as the art design example. Again, I’m pretty sure this isn’t the only way how I could’ve modeled it, but for now there were strong indicators this was a viable option. Below another, more convoluted – it’s the last, I promise – example of what I’ve come up with.

public class PortfolioAccessPolicy
{
  public bool CanEdit(Subject subject)
  {
    return 
      subject.CanEditPortfolio(RolePermissionSet) && 
      subject.IsStarterOfPortfolio(StarterId);
  }

  public bool CanDelete(Subject subject)
  {
    return 
      subject.CanDeletePortfolio(RolePermissionSet) && 
      subject.IsStarterOfPortfolio(StarterId);
  }

  public bool CanViewItems(Subject subject)
  {
    return 
      subject.CanViewPortfolioItems(RolePermissionSet) ||
      subject.IsPortfolioSupervisor();
  }
}

//Usage on the read side
public class PortfolioResource 
{
  public PortfolioResource CompleteAuthorization(Subject subject, PortfolioAccessPolicy policy, UrlHelper helper)
  {
    var links = new List<Link>();
    if(policy.CanEdit(subject)) 
      links.Add(new Link { Rel = "edit", Href = helper.GetLink<PortfolioResourceController>(_ => _.Put(Id)) });
    if(policy.CanDelete(subject)) 
      links.Add(new Link { Rel = "delete", Href = helper.GetLink<PortfolioResourceController>(_ => _.Delete(Id)) });
    if(policy.CanViewItems(subject)) 
      links.Add(new Link { Rel = "items", Href = helper.GetLink<PortfolioResourceController>(_ => _.GetItems(Id)) });

    return new PortfolioResource(Id, Name, Season, SubsidiaryId, links.ToArray());
  }
}

//Usage on the write side
public class EditPortfolioAuthorizer : Authorizes<EditPortfolio>
{
  public void Authorize(Subject subject, EditPortfolio message)
  {
    var policy = this.policyRepository.Get(new PortfolioId(message.PortfolioId));
    if(!policy.CanEditPortfolio(subject))
      throw new NotAuthorizedException("The caller is not authorized to edit this portfolio.");
  }
}

Conclusion

My only hope in writing this down is that it will make you think about how you’re modeling authorization and the realization that there is no one way of modeling it. Whether you’re rolling your own or dealing with something off the shelf, know that your requirements will dictate a lot. If you’re in an environment where reads and writes are separated but crave some commonality, know there are ways to share. I do not pretend to have all the answers, merely sharing what I’ve learned so far and what worked for me.

Advertisements

Viewmodels.js

Over the past couple of weeks I’ve been entrenched in UI development, learning – the good parts of – javascript and using libs like Knockout.js, jQuery and Twitter BootStrap. My relationship with javascript, which started when Netscape Navigator was still cool, has been on and off over the past years. Given its current rise, it seemed like a good idea to get intimate again. Tasked with building the administrative part of an app that has a fair bit of client-side behavior, we set out to build the first version euh … failure. It was monolithic, taking on way too many responsibilities in places you didn’t expect, it had hardly any extensibility points (let alone figuring out where to find them), it was overloaded with technical “look at my neat trick to make it bark” concerns, and – to top it off – it was borderline unreadable. It’s safe to say we weren’t very happy with the result (which would haunt us in the months and years to come). Although, in all fairness, there were some nice gems in there.

So, what were we todo? What any agile team should do … be honest about it and tackle it head on. I started hacking on what I thought “it should be more like” and so did another team member. We sat down as a team, did a code review of the current approach to better understand what the actual problems were, compared it with two more approaches, sollicited feedback, bounced off ideas on how to improve the situation, and settled on a path forward.

It’s this path forward I want to show you a glimpse of. This is a more in depth exploration of what I touched upon in viewmodels like you meant it, but in a slightly different context.

Building blocks

If you want to prevent logic from creeping into your view, if you want to clearly define how visual state relates to a piece of data or to a trigger, you should refrain from using primitive types in your viewmodel, or you’ll end up with something similar to the sample below.

Preventing conditions from creeping into views is easy to solve. You just move the condition into the viewmodel, giving it a descriptive name along the way, maybe using a computed observable if it happens to be dynamic. Easy enough and nothing wrong with doing so (in fact I encourage you to do so). What may be less obvious is how to get rid of the primitives. The answer is simple: building the right abstractions.

Being valid or invalid, having focus, visibility, being enabled or disabled, having a value, change tracking and – in the case of data-binding – being able to pause and dispose subscriptions are all concerns that revolve around that one value most people seem to put as a primitive in their viewmodel. Don’t get me wrong, I’m not suggesting that each and every primitive value requires all these concerns, but I’d rather put them in my abstraction than polluting my viewmodel when I do need them.
Next to TextInput and NumericInput you can imagine DateInput, TimeInput, SelectOneInput, SelectManyInput, but also more domain specific inputs like PercentageInput (constrained numeric input), MinDefaultMaxDurationsInput (composite input), or even HtmlColorInput (formatted text input).

What applies to inputs also applies to triggers, which are most commonly visualized as buttons and links. A trigger could be used to replace the CanSave observable and Save function in the first example.

It’s nice to build and have these abstractions, it’s even better to actually use them. Do realize that, like any abstraction, they emerged from actual viewmodel implementations. For those that think “this is a controls library”, I can only feel pitty, because this has little to do with view specific controls.

Composition is the name of the game

This was one of the most fundamental insights we had during this journey: the ability to compose viewmodels using the above building blocks and other viewmodels (that in turn have been composed using those building blocks) made them easier to define and reason about. It also helps to keep them at the same level of abstraction.

This line of thought can also be applied at other levels. It has a very recursive and composite feel to it.

Behavior and low-carb viewmodels

Something that’s been missing from the previous snippets is any form of actual behavior. How is one going to introduce logic/flow and interact with dialogs and server side resources? I tend to associate that coordination task with a controller. “Viewmodels as controllers” starts to crumble really fast when you’ve applied the composition above. You’ll end up with big fat viewmodels that feel like a blatant SRP violation.
Therefor making both the controller and how it’s bound to viewmodels explicit, pays off in readability, testability, and keeping the viewmodels thin. Don’t get too hung up on the details below, there are many ways to implement these concerns.

Below is a glimpse of what the final binding could look like.

Conclusion

This is still very much a WIP, but with what little abstractions we’ve come up with, we’ve been able to untangle most of the mess we were in before. Dependencies are now easy to replace (adhering to SRP pays off). Composition has simplified the amount of detail we have to worry about at each level of abstraction. Making the controller explicit has kept our viewmodels free of unnecessary dependencies. All in all we’ve been much more productive using this approach.

Viewmodels like you meant it

Although a lot has been written on the topic of viewmodels – both in the MVVM(*) and MVC UI pattern space – it strikes me as odd how underused they are. Why muck around with resource files and attribute based validation hints inside your views when you could provide them with a rich viewmodel? Most people tend to think about their viewmodels in terms of input, either directly (e.g. the value of a textbox) or indirectly (e.g. one of the values you choose from a select, or a list from which to select a row). Why isn’t output equally as important? Why are those magic strings in the view not part of the viewmodel? Why don’t people question the implicit routing and navigation that goes into their views?

Not including output in a view model and primitive obsession are the main reasons why logic and other dependencies creep into your view.

Let’s take the screen of my post on statecharts as an example.

Master Detail UI - Example
What most people end up with – if they even put in the effort to craft an explicit view model – is something along these lines.

public class TrainStationModel {
  public string FilteredBy { get; set; }
  public List TrainStations { get; set; }
  public TrainStationDetails SelectedTrainStation { get; set; }
}

public class TrainStationItem {
  public Guid TrainStationId { get; set; }
  public string Name { get; set; }
  public string Abbreviation { get; set; }
}

public class TrainStationDetails {
  public Guid TrainStationId { get; set; }
  [Required, StringLength(80, MinLength = 1)]
  public string Name { get; set; }
  [Required, StringLength(40, MinLength = 1)]
  public string Abbreviation { get; set; }
  [Required, Range(0,360)]
  public double Longitude { get; set; }
  [Required, Range(0,360)]
  public double Latitude { get; set; }
}

If we’d want to I18N this screen it would require either a view per culture or a culture+resource aware view. For client-side validation it requires reflective helpers to get the meta-data, sprinkled as attributes on those model properties. Routing and navigation creep into the view as behaviors need to be executed.

Contrast this with what I’ve been using as viewmodels – below – for the past 7 years.

  public class TrainStationAdministrationModel : Page {
    readonly TextOutputField _header;
    readonly TrainStationBodyModel _body;

    public TrainStationAdministrationModel() : base("trainstationadministration") {
      _header = new TextOutputField("header", 
        new Text(new StringResource("EurostarStationsKey")));
      _body = new TrainStationBodyModel("body");
    }

    public TextOutputField Header {
      get { return _header; }
    }

    public TrainStationBodyModel Body {
      get { return _body; }
    }
  }

  public class TrainStationBodyModel : ModelItem {
    readonly Trigger<TextInputField> _filterByTrigger;
    readonly TrainStationDetailModel _detail;
    readonly SelectList<TrainStationListItem> _list;
    readonly Trigger<TextOutputField> _newTrigger;
    
    public TrainStationBodyModel(string id) : base(id) {
      _detail = new TrainStationDetailModel("detail");
      _list = new SelectList<TrainStationListItem>("list");
      _newTrigger = new Trigger<TextOutputField>("newtrigger", "new", 
        new TextOutputField("newtext", 
          new Text(new StringResource("NewTriggerKey"))));
      _filterByTrigger = new Trigger<TextInputField>("filterbytrigger", "filterby",
        new TextInputField("filterbytext", 
          new Text(new StringResource("FilterByKey"))));
    }

    public Trigger<TextInputField> FilterByTrigger {
      get { return _filterByTrigger; }
    }

    public SelectList<TrainStationListItem> List {
      get { return _list; }
    }

    public TrainStationDetailModel Detail {
      get { return _detail; }
    }

    public Trigger<TextOutputField> NewTrigger {
      get { return _newTrigger; }
    }
  }

  public class TrainStationListItem : SelectListItem {
    readonly Trigger<TextOutputField> _deleteTrigger;
    readonly TextOutputField _name;
    readonly TextOutputField _abbreviation;

    public TrainStationListItem(string id) : base(id) {
      _name = new TextOutputField("name", 
        Text.Empty);
      _abbreviation = new TextOutputField("abbreviation",
        Text.Empty);
      _deleteTrigger = new Trigger<TextOutputField>(
        "deletetrigger",
        "delete",
        id,
        new TextOutputField("deletetext", 
          new Text("DeleteTriggerKey")));
    }

    public TextOutputField Abbreviation {
      get { return _abbreviation; }
    }

    public TextOutputField Name {
      get { return _name; }
    }

    public Trigger<TextOutputField> DeleteTrigger {
      get { return _deleteTrigger; }
    } 
  }

  public class TrainStationDetailModel : ModelItem {
    readonly TextOutputField _header;
    readonly TrainStationDetailNamingModel _naming;
    readonly TrainStationDetailGeolocationModel _geolocation;
    readonly Trigger<TextOutputField> _saveTrigger;
    readonly Trigger<TextOutputField> _cancelTrigger;

    public TrainStationDetailModel(string id) : base(id) {
      _header = new TextOutputField("header", 
        new Text(new StringResource("ChangeTheSettingsKey")));
      _naming = new TrainStationDetailNamingModel("naming");
      _geolocation = new TrainStationDetailGeolocationModel("geolocation");
      _saveTrigger = new Trigger<TextOutputField>(id, "add",
        new TextOutputField("savetrigger",
          new Text(new StringResource("SaveTriggerKey"))));
      _cancelTrigger = new Trigger<TextOutputField>(id, "add",
        new TextOutputField("canceltrigger",
          new Text(new StringResource("CancelTriggerKey"))));
    }

    public TextOutputField Header {
      get { return _header; }
    }

    public TrainStationDetailGeolocationModel Geolocation {
      get { return _geolocation; }
    }

    public TrainStationDetailNamingModel Naming {
      get { return _naming; }
    }

    public Trigger<TextOutputField> SaveTrigger {
      get { return _saveTrigger; }
    }

    public Trigger<TextOutputField> CancelTrigger {
      get { return _cancelTrigger; }
    }
  }

  public class TrainStationDetailNamingModel : ModelItem {
    readonly TextOutputField _header;
    readonly TextInputField _name;
    readonly TextInputField _abbreviation;
    
    public TrainStationDetailNamingModel(string id) : base(id) {
      _header = new TextOutputField("header", 
        new Text(new StringResource("NamingKey")));
      _name = new TextInputField("name", 
        new Text(new StringResource("NameKey")));
      _abbreviation = new TextInputField("abbreviation", 
        new Text(new StringResource("AbbreviationKey")));
    }

    public TextOutputField Header {
      get { return _header; }
    }

    public TextInputField Name {
      get { return _name; }
    }

    public TextInputField Abbreviation {
      get { return _abbreviation; }
    }
  }

  public class TrainStationDetailGeolocationModel : ModelItem {
    readonly TextOutputField _header;
    readonly DoubleInputField _longitude;
    readonly DoubleInputField _latitude;

    public TrainStationDetailGeolocationModel(string id) : base(id) {
      _header = new TextOutputField("header", 
        new Text(new StringResource("GeolocationKey")));
      _longitude = new DoubleInputField("longitude", 
        new Text(new StringResource("LongitudeKey")));
      _latitude = new DoubleInputField("latitude", 
        new Text(new StringResource("LatitudeKey")));
    }

    public TextOutputField Header {
      get { return _header; }
    }

    public DoubleInputField Longitude {
      get { return _longitude; }
    }

    public DoubleInputField Latitude {
      get { return _latitude; }
    }
  }

Notice the lack of primitives and attributes, the presence of output fields, and routing/navigation using triggers. Input fields cater for values, change tracking, field level validation, provisioning of meta-data to be used as UX enhancements. The explicitness of the models allows for strongly-typed partial views. Text fields and formatted values work together with the current UI culture and resources to allow for seamless I18N. Making viewmodels visitable (e.g. solves the puzzle of how to capture form input in a model) and queryable (e.g. lookup of items in the model – hence the identifiers) will allow for even greater power inside controllers. Triggers and associated events work nicely together with a hierarchic statechart to control flow. The use of standardized model types allows for augmenting productivity in views by introducing helpers.

Conclusion

As always, I left out a lot of the finer details. If there’s one thing that should stick, it’s to take a deeper, harder look at how you’ve been dealing with viewmodels …

(*): not discussed here.