May 2019

Volume 34 Number 5

[The Working Programmer]

Coding Naked: Naked Collections

By Ted Neward | May 2019

Ted NewardWelcome back, NOFers. Last time, I augmented the Speaker domain type with a number of properties, along with a number of annotations and conventions about those properties that provide hints (or, to be more honest, directions) to the UI as to how to validate or present those properties to the user. One thing I didn’t discuss, however, is how a given domain object can have references to more than one of something. For example, Speakers often have multiple Talks they can give, or can profess expertise in one or more Topics. NOF refers to these as “collections,” and there are a few rules surrounding how they work that are a little different from the previous conversation.

Let’s see about giving Speakers some Talks and Topics, shall we?

Naked Concepts

To start with, abandon all arrays, all ye who enter here. NOF doesn’t make use of arrays for collection properties, and instead relies entirely on collection objects (IEnumerable<T>-derived) to hold zero-to-many of some other type. The NOF manual strongly recommends that these collections be strongly typed (using generics), and NOF doesn’t allow for multiple associations of value types (like strings, enumerated types and so on) because NOF believes that if the type is something “important” enough to warrant association, it should be a full-fledged domain type.

Thus, for example, if I want to capture the notion of a Topic (like “C#,” “Java” or “Distributed Systems”) in the conference system, where other programming approaches may allow you to get away with a simple “list-of-strings” as a property type, NOF insists that Topic be a full domain object type (which is to say, a public class with properties), complete with its own domain rules. It’s reasonable that the list of Topics might be a fixed set, however, so I’ll seed the database with the complete set of Topics that my conference wants to consider.

Likewise, although a Talk could be just a title, it’s really a series of things: a title, a description, a Topic (to which it belongs or refers), and is given by one (or more) Speakers. Clearly, I have a little domain modeling in front of me yet.

Naked Collections

In many ways, the easiest way to begin is with the domain classes for Talk and Topic themselves, absent any connections between them (or Speakers). By now, much of what I write here for each of these should be pretty trivial and straightforward, as Figure 1 shows.

Figure 1 Domain Classes for Talk and Topic

public class Talk
  {
    [NakedObjectsIgnore]
    public virtual int Id { get; set; }
    [Title]
    [StringLength(100, MinimumLength = 1,
       ErrorMessage = "Talks must have an abstract")]
    public virtual string Title { get; set; }
    [StringLength(400, MinimumLength = 1,
       ErrorMessage = "Talks must have an abstract")]
    public virtual string Abstract { get; set; }
  }
  public class TalkRepository
  {
    public IDomainObjectContainer Container { set; protected get; }
    public IQueryable<Talk> AllTopics()
    {
      return Container.Instances<Talk>();
    }
  }
  [Bounded]
  public class Topic
  {
    [NakedObjectsIgnore]
    public virtual int Id { get; set; }
    [Title]
    [StringLength(100, MinimumLength = 1,
       ErrorMessage = "Topics must have a name")]
    public virtual string Name { get; set; }
    [StringLength(400, MinimumLength = 1,
       ErrorMessage = "Topics must have a description")]
    public virtual string Description { get; set; }
  }
  public class TopicRepository
  {
    public IDomainObjectContainer Container { set; protected get; }
    public IQueryable<Topic> AllTopics()
    {
      return Container.Instances<Topic>();
    }
  }

So far, this is pretty straightforward. (There’re obviously other things that could and/or should be added to each of these two classes, but this gets the point across pretty well.) The one new attribute used, [Bounded], is an indication to NOF that the complete (and immutable) list of instances can and should be held in memory on the client, and presented to the user as a dropdown list from which to choose. Correspondingly, then, the full list of Topics needs to be established in the database, which is easiest done in the DbInitializer class from the “SeedData” project, in the Seed method (as discussed in prior columns in this series), shown in Figure 2.

Figure 2 Creating a List of Topics

protected override void Seed(ConferenceDbContext context)
{
  this.Context = context;
  Context.Topics.Add(new Topic() { Name = "C#",
    Description = "A classical O-O language on the CLR" });
  Context.Topics.Add(new Topic() { Name = "VB",
    Description = "A classical O-O language on the CLR" });
  Context.Topics.Add(new Topic() { Name = "F#",
    Description = "An O-O/functional hybrid language on the CLR" });
  Context.Topics.Add(new Topic() { Name = "ECMAScript",
    Description = "A dynamic language for browsers and servers" });
  Context.SaveChanges();
  // ...
}

This will provide a (rather small, but useful) list of Topics from which to work. By the way, if you’re playing the home game and writing the code by hand, remember to add the TalkRepository to the main menu by adding it to the MainMenus method in Naked­ObjectsRunSettings.cs in the Server project. In addition, make sure that the two Repository types are also listed in the Services method in the same file.

Fundamentally, a Talk is given by a Speaker and is on a given Topic. I’m going to ignore the more complicated scenario when a Talk is given by two Speakers, or if a Talk will cross multiple Topics, to keep it simple for the time being. Thus, for the first step, let’s add Talks to the Speaker:

private ICollection<Talk> _talks = new List<Talk>();
public virtual ICollection<Talk> Talks
{
  get { return _talks; }
  set { _talks = value; }
}

If you build and run the project, you’ll see “Talks” show up as a collection (table) in the UI, but it will be empty. I could, of course, add some Talks in SeedData, but in general, Speakers need to be able to add Talks to their profiles.

Naked Actions

This can be done by adding an action to the Speaker class: A method that will appear, “magically,” as a selectable item in the “Actions” menu when a Speaker object is in the display. Like properties, actions are discovered via the magic of Reflection, so all that needs to happen is to create a public method on the Speaker class:

public class Speaker
{
  // ...
  public void SayHello()
  {
  }
}

Now, when built and run, after bringing up a Speaker, the “Actions” menu is displayed and inside of it, “SayHello” appears. Currently it does nothing; it would be nice, as a starting point, to at least put a message back to the user. In the NOF world, this is done by making use of a service, an object whose purpose is to provide some additional functionality that doesn’t belong to a particular domain object. In the case of general “messages back to the user,” this is provided by a generic service, defined by NOF itself in the IDomainObjectContainer interface. I need an instance of one of these in order to do anything, however, and NOF uses dependency injection to provide one on demand: Declare a property on the Speaker class of type IDomainObjectContainer, and NOF will make sure each instance has one:

public class Speaker
{
  public TalkRepository TalkRepository { set; protected get; }
  public IDomainObjectContainer Container { set; protected get; }

The Container object has an “InformUser” method used to convey general messages back to the user, so using it from the SayHello action is as simple as:

public class Speaker
{
  // ...
  public void SayHello()
  {
    Container.InformUser("Hello!");
  }
}

But I started with a desire to allow the user to add a Talk to a given Speaker’s repertoire; specifically, I need to capture the title of the talk, the abstract (or description, because “abstract” is a reserved word in C#), and the Topic to which this Talk belongs. Calling this method “EnterNewTalk,” then, I have the following implementation:

public void EnterNewTalk(string title, string description, Topic topic)
{
  var talk = Container.NewTransientInstance<Talk>();
  talk.Title = title;
  talk.Abstract = description;
  talk.Speaker = this;
  Container.Persist<Talk>(ref talk);
  _talks.Add(talk);
}

Several things are happening here, so let’s unpack. First, I use the IDomainObjectContainer to create a transient (non-persisted) instance of the Talk. This is necessary because NOF needs to be able to inject “hooks” into each domain object in order to work its magic. (This is why all properties must be virtual, so that NOF can manage the UI-to-object synchronization, for example.) Then, the Talk’s properties are set, and the Container is used again to Persist the talk; if this isn’t done, the Talk isn’t a persistent object and won’t be stored when I add the Talk to the Speaker’s list of Talks.

It’s fair to ask, however, how the user specified this information to the EnterNewTalk method itself. Once again, the wonders of Reflection are at work: NOF dug the parameter names and types out of the method parameters, and constructed a dialog to capture those items, including the Topic itself. Remember when Topic was annotated with “Bounded”? That instructed NOF to build the list of Topics in this dialog to be a dropdown list, making it incredibly easy to select a topic from the list. (It should be easy to infer at this point that as I add Topics to the system, they’ll all be added to this dropdown list without any additional work required.)

Now, it’s reasonable to suggest that creating Talks should be something supported by the TalkRepository, not on the Speaker class itself, and, as you can see in Figure 3, it’s an easy refactor to do.

Figure 3 Supporting Talk Creation with TalkRepository

public class Speaker
  {
    public TalkRepository TalkRepository { set; protected get; }
    public IDomainObjectContainer Container { set; protected get; }
    public void EnterNewTalk(string title, string description, Topic topic)
    {
      var talk = TalkRepository.CreateTalk(this, title, description, topic);
      _talks.Add(talk);
    }
  }
  public class TalkRepository
  {
    public IDomainObjectContainer Container { set; protected get; }
    public Talk CreateTalk(Speaker speaker, string title, string description,
                Topic topic)
    {
      var talk = Container.NewTransientInstance<Talk>();
      talk.Title = title;
      talk.Abstract = description;
      talk.Speaker = speaker;
      Container.Persist<Talk>(ref talk);
      return talk;
    }
  }

What’s more, by doing this, the “Talks” menu will be automatically adorned with a new menu item, “CreateTalk,” which will—again, through the magic of Reflection—automatically create a dialog to enter the data necessary to create a Talk. Speakers, of course, can’t just be typed in, so NOF will make that a “droppable” field, meaning that NOF will expect a Speaker object to be dragged-and-dropped into this field. (To see this in action in the default NOF Gemini interface, fire up the app, select a Speaker, then click the “Swap Pane” button—the double-arrowed button at the bottom of the display. The Speaker selected will shift to the right of the screen, and the Home interface will appear, allowing selection of the “Talks/Create Talk” item. Drag the Speaker’s name to the Speaker field in the Create Talk dialog, and voila—the Speaker is selected.)

It’s absolutely crucial to understand what’s happening here: I now have two different UI paths (creating a Talk from the top-level menu, or creating a Talk from a given Speaker) that allow for two different user-navigation scenarios, with little effort and zero duplication. The TalkRepository worries about all things “CRUD” and “Talk” related, and the Speaker uses that code, all while keeping the user interaction entirely within the Speaker if that’s how the user wants to arrange it.

Wrapping Up

This is not your grandfather’s UI toolkit. In only a few lines of code, I have a workable interface (and, considering that data is being stored and retrieved from a standard SQL back end, a data storage system) that could, at least for the moment, be used directly by users. More importantly, none of this is in a proprietary format or language—this is straight C#, straight SQL Server, and the UI itself is Angular. There are a few more things to discuss about NOF’s default interface, which I’ll get to in the next piece, including authentication and authorization facilities. In the meantime, however … happy coding!


Ted Neward is a Seattle-based polytechnology consultant, speaker and mentor. He has 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