June 2019

Volume 34 Number 6

[The Working Programmer]

Coding Naked: Naked Acting

By Ted Neward | June 2019

Ted NewardWelcome back, NOFers. Last time, I examined how NakedObjects handles collections, which provide the ability to associate a number of objects with one another (msdn.com/magazine/mt833439). As I went through that piece, however, I introduced the concept of an “action” into the code without really explaining it in any detail. Because actions are the “code” pairing to properties’ “data,” it makes sense to go over actions in a bit more detail—and in particular, note the places where actions can live and how they appear in the UI.

Ready to act naked?

Actions

The NOF manual describes an action as “a method that is intended to be invoked by a user.” It’s important to note, though, that these methods aren’t just user-invokable; the manual goes on, in the same sentence, to point out that “it may also be invoked programmatically from within another method or another object.” Actions, then, are where behavior on an object is defined, and NOF will generate a menu item for the user to click/invoke for each action it discovers.

One such action that easily comes to mind is the idea of per­forming the basic CRUD behaviors on a given Talk (or Speaker, for that matter), but recall that NOF already handles those as part of the core behavior of the UI as a whole. A menu item off the main menu allows you to create a new Speaker, which, remember, is defined on the SpeakerRepository, as you saw in the second article in this series (msdn.com/magazine/mt833268). Editing that speaker happens after a Speaker is selected and the user selects the “Edit” menu item that appears. In fact, the only thing missing from the list is the ability to delete a Speaker, so that’s an easy place to start—I’ll now add an Action that allows me to delete the currently visible Speaker.

(I know, I know—who would ever want to delete a Speaker? We’re all so lovable, it’s hard to imagine that anyone would ever want to remove a Speaker from the system. But go with me on this one, for pedagogical purposes if nothing else. Imagine they’re retiring. Or something.)

To add a new action, I only need to add a new method to the Speaker type. By default, any public method on a domain object will be exposed as an action in the “Actions” menu of the UI, so adding this is pretty straightforward:

public void Delete()
{
  Container.DisposeInstance(this);
}

Recall that the Container property is an instance of the IDomain­ObjectContainer that’s dependency injected onto each Speaker after it’s created or returned from the SpeakerRepository. To remove a persisted object (such as a Speaker that was added with the CreateNewSpeaker action on the SpeakerRepository), the Container has to “dispose” of that instance.

However, when I add this to the Speaker class and run it, something bad happens—specifically, after deleting the Speaker, I get an error about a missing object. The NOF client is complaining that the domain object it was just displaying—the Speaker that was just deleted—no longer exists. That makes a ton of real sense, when you think about it, and the answer is to give the UI something else to display, such as the list of all the remaining Speakers in the system, which I can get easily enough from the Container again, like so:

public IQueryable<Speaker> Delete()
{
  Container.DisposeInstance(this);
  return Container.Instances<Speaker>();
}

Now, when the user selects to delete a Speaker, the UI removes them, shows the remainder of the list of Speakers, and life moves on. (Farewell, sweet Speaker. We remember you fondly.)

One problem that emerges, however, is that certain domain objects have a clear dependency on others within the system, and arbitrarily destroying those objects without thinking about those associations will sow chaos. (In fact, NOF will prevent deletion of an object that has any associated objects until those associated objects are deleted first.) In practical terms, this means if the Speaker has any Talks created, deleting the Speaker will leave those Talks orphaned. Ideally, you wouldn’t allow Speakers who have one or more Talks created to be deleted. You could put a check inside the Delete method, and provide an error message if the user selects it and you can’t follow through, but it’s generally a better idea to disallow a user the option of even selecting something they’re not allowed to do—in NOF parlance, you want to disable the action entirely.

Once again, NOF uses naming conventions to provide this kind of functionality. By providing a method that begins with the name “Disable” and continues with the name of the action you want to disable (in this case “Delete”), you can do that check long before the user ever selects it:

public string DisableDelete()
{
  if (Talks.Count > 0)
  {
    return "Speakers cannot be deleted until their Talks are removed.";
  }
  return null;
}

Notice that DisableDelete returns a string, which will either be null if the action is fine to execute, or will be displayed to the user. (By default, the menu item for the action will be disabled, so it won’t be selectable by the user, but remember that actions can be invoked in other ways.)

What if you want to hide the action from the user entirely? That’s a different question, but NOF uses the same approach—a HideDelete method—to indicate whether the action should be displayed. The choice between hidden and disabled is obviously a subtle one, and probably a UX debate that others are more qualified to have than I; suffice it to say, choose as your UX designer or your heart takes you.

Action Parameters

Keep in mind that, in many cases, the action will require additional data before it can be carried out. An action may want to find all Speakers who have Talks that contain a particular search term, such as “Blockchain” or “IoT” (hopefully to remove them entirely from the system), which means the user has to have an opportunity to type in that search term. Again, NOF is committed to the principle that the UI should be built from the structure of the code itself, which means in this case that the UI will examine the parameters of the method and present a UI element that provides the user the chance to put the necessary input in place. You’ve seen this mechanism at work already, in that the FindSpeakerByLastName method of the SpeakerRepository does the same thing: accepts a string parameter for the last name for which to search, which NOF interprets to mean it needs to display a textbox after that action is invoked.

Naked Contributions

Recall from the earlier article I mentioned that the Naked Objects world consists of both domain objects and services, where services are collections of behavior that don’t necessarily belong on the domain object. Essentially, if a method on a service takes a domain object as one of its parameters, and that parameter has a “ContributedAction” attribute on it, the NOF UI takes that to mean that this action should appear on the domain object’s menu.

Imagine for a moment that you want the code to notify Speakers that their talk was accepted at the conference to be outside the Speaker object. (This makes sense, actually, from a domain modeling perspective, because notifying a Speaker has really nothing to do with being a Speaker.) You create a service, then, that looks something like this (taking care to make sure it’s registered with NOF in the Services property of the NakedObjectsRunSettings class, as well):

public class NotificationService
{
  public IDomainObjectContainer Container { set; protected get; }
  public void AcceptTalk([ContributedAction] Talk talk)
  {
    Container.InformUser("Great! The Speaker will be emailed right now");
  }
}

The implementation of the notification, of course, will take more than just showing a message to the user, but that’s not the important part—the key here is the ContributedAction attribute on the Talk parameter, which will cause this action to appear in the Talk list of actions. This provides a great deal of flexibility regarding where code appears in the system, compared to where it appears in the UI.

Note that it’s possible to contribute actions to groups of domain objects, as well, using the same kind of convention, only applying it to a parameter that’s a collection of domain objects rather than a single one. For example, if I want to send notifications to a collection of Speakers, I can do so from a given method, like this:

public void NotifySpeakers([ContributedAction] IQueryable<Speaker> speakers)
{
  foreach (Speaker s in speakers)
  {
    // Send SMS or email or whatever
  }
}

From a UI perspective, any collection of Speakers will now have a checkbox appear in front of each one (allowing the user to select which of the Speakers will appear in the collection), and the “Actions” menu presented in the table will have the “Notify Speakers” action displayed. As with all actions, if additional input from the user is required (such as the message to send each Speaker), this will appear as part of the UI before the method is executed.

Wrapping Up

Actions represent an important aspect of the Naked Objects Framework experience, in that they’re where the vast majority of the “business rules” of any application will sit. Services can provide opportunities to host business logic, to be sure, but it’s inside actions on a domain object that domain-driven developers will find the best opportunities to provide the code to “do the work.”

Happy coding!


Ted Neward is a Seattle-based polytechnology consultant, speaker and mentor. He’s written a ton of articles, authored and co-authored a dozen books, and speaks all over the world. Reach him at ted@tedneward.com or read his blog at blogs.tedneward.com.

Thanks to the following technical expert for reviewing this article: Richard Pawson


Discuss this article in the MSDN Magazine forum