Fundamentum

Let me start by saying that ASP.NET MVC is terrific. Before ASP.NET MVC, one had to use ASP.NET, which I hope the universe will soon forget, so moving to ASP.NET MVC is like using a tablet instead of a clay tablet (except I am overly generous towards ASP.NET with the analogy). Really. There is no reason not to use ASP.NET MVC for your Web applications – if you enter a time machine and land ten years ago, when Struts on Java was already old news.

We live in 2013 now, folks, and the Web is moving forward. So, what is a dot-net Web programmer supposed to do? This post is a particular answer to that question I gave myself a few moons ago. Note that it is a particular, idiosyncratic even, answer and is meant to implement CRUD UIs, not any Web app in general. My answer was the Fundamentum framework, the code of which I will not open-source at this time.

Fundamentum assumes your Web application is built according to the client-side MVC or MVP pattern. It currently uses Backbone.js as its underlying Javascript framework, and offers a Backbone View and a Backbone Model to extend but it could be adapted to any other similar framework. Templating is done with the Handlebars.js library.

The application has no “dirty” state after each edit, meaning, there are never data that must be “submitted” to the server with an explicit later action (although it can have “stale” data, if other users are editing the same entities).

ASP MVC 4 is currently used to provide navigation, authentication, authorization and basic layout, while the content-centric parts of each page are rendered via Backbone views. It relies on server-side support for its Ajax calls, and the current implementation contains C# utilities and base classes to use to implement ASP MVC Controllers with the needed functionality.

Declarative annotations for editing

Fundamentum allows the declarative annotation of editable content in the page, whether in the “static” or “templated” parts of the markup. Annotation is done using special classes. Later on, a full grammar for the annotations will be presented. As an example, the following annotation instructs Fundamentum to create in-place editing functionality for whatever markup is generated by the Carrier partial template, and informs Fundamentum to create specifically a “select” list for the Carrier property of the model, and use the Carrier partial template to render each “option” in the list.

<span class="edit attr:Carrier select tmpl:Carrier">{{#with Carrier}} {{> Carrier }} {{/with}}</span>

Any property in the model can be edited, however deep in the object graph. Model properties are actually specified using Spring Expressions. An example of a more complicated expression is the following, which reaches inside the PurchasePlanPayment collection to find a particular element and refers to its ChargeUnit property.

PurchasePlanPayments.^{PurchasePlanPaymentID==new System.Guid('{{ PurchasePlanPaymentID }}')}.ChargeUnit

Fundamentum allows editing of single attributes, in preference to the usual Backbone practice of updating the whole model at once. It actually supplements the default Backbone Ajax API
(the set of calls that Backbone does by default and expects the server to understand) with a new one, which updates a single property and is caused by the saveAttribute(attributeName, value, callback) model method.

PUT model-url-root / model-id / property-name HTTP/1.1
... HTTP Headers ...

JSON rendering of the new value

It also supplements the default Backbone API with a call to return available options in the “select” list for a property, like in the initial example.

GET model-url-root /OPTIONS/ model-id / property-name HTTP/1.1
... HTTP Headers ...

A third addition is the support for cloning, using the following call, caused by the createClone() model method.

POST model-url-root  / model-id /Clone HTTP/1.1
... HTTP Headers ...

Finally, there is support for adding a new element in a collection property with the following call, caused by the pushAttribute(attributeName, value, callback) model nethod.

POST model-url-root / model-id / property-name HTTP/1.1
... HTTP Headers ...

Server-side support for responding to these calls will be presented shortly. You can read about standard Backbone model methods and associated HTTP calls at Backbone Tutorials.

Edit Annotation Grammar

The essential annotation for editable content is class edit. On its own, it invokes a simple, in-line editor, as in the following.

<span class="edit attr:SubscriptionPlan.DurationNumberofUnits">{{ SubscriptionPlan.DurationNumberofUnits}}</span>

Class edit must appear together with a class which is the property expression preceded by attr: (NB. the colon character). Since the whole must be a valid class name, and will also become part of a URL in the Ajax call, the property expression must be suitably URL-escaped, as in the example of a complicated expression above, where the space character inside the expression new Guid was replaced by %20.

Class select, as we’ve seen, invokes a “select” list containing available options fetched from the server. In most cases, this must appear together with a class which is the token tmpl: (NB. the colon character) suffixed by the name of the Handlebars partial template to use in order to render each option (which is returned as a JSON object). The latter is usually the same template used to render the editable content in the first place, unless a different presentation is needed. The tmpl: class is not needed if the OPTIONS call is overriden to return a character string, which will be taken as an already formatted option HTML text.

Class datepicker invokes a date-time picker.

Class checkbox invokes a checkbox control. In the application using Fundamentum, which uses a Metro-style, tile-based interface, the checkbox control has been overriden with an actionable tile activity flag.

Finally, class wysiwyg invokes a rich-text editor (jWysiwyg).

All in-place editing is done using jEditable, with some custom additions.

Fundamentum Controllers

A Fundamentum Controller (ASP MVC Controller) is needed per basic entity, where a basic entity is any entity which is at the root of an object graph that we deem necessary to manipulate as a whole. Except for those classes which happen to be basic entities themselves, and will end up with a Controller each, any classes that represent “value entities”, which don’t have an identity separate from the basic entity from which they stem, don’t (and shouldn’t) have a Controller dedicated to them.

I found AttributeRouting a very useful notation for HTTP routes, and it is used exclusively in all Fundamentum Controllers.

In fact, I have created a special attribute, FundamentumRouteConventionAttribute, inheriting from RouteConventionAttributeBase, that annotates controllers to give them convention-based routes (ignore the parts about tags, as I’ll not elaborate on them in this post).

        private static readonly List<FundamentumRouteConventionInfo> Conventions = new List<FundamentumRouteConventionInfo>
        {
            new FundamentumRouteConventionInfo("Index", "GET", ""),
            new FundamentumRouteConventionInfo("Get", "GET", "{id}"),
            new FundamentumRouteConventionInfo("Delete", "DELETE", "{id}"),
            new FundamentumRouteConventionInfo("Update", "PUT", "{id}"),
            new FundamentumRouteConventionInfo("Create", "POST", ""),
            new FundamentumRouteConventionInfo("Clone", "POST", "{id}/Clone",9),
            new FundamentumRouteConventionInfo("SetAttributeValue", "PUT", "{id}/{AttributeName}"),
            new FundamentumRouteConventionInfo("AttributeCollectionAddNew", "POST", "{id}/{AttributeName}"),
            new FundamentumRouteConventionInfo("AttributeOptions", "GET", "OPTIONS/{id}/{AttributeName}"),
            new FundamentumRouteConventionInfo("GetTagsAvailable", "GET", "TAGS/ALL",9),
            new FundamentumRouteConventionInfo("GetTags", "GET", "TAGS/{id}"),
            new FundamentumRouteConventionInfo("RemoveTag", "DELETE", "TAGS/{id}/{Tag}"),
            new FundamentumRouteConventionInfo("AddTag", "PUT", "TAGS/{id}/{Tag}")
        };

All Fundamentum Controllers inherit from GenericEditController, which offers basic functionality for implementing Backbone and Fundamentum calls. The GenericEditController is parameterized by the class of the entity and the class of its key. We also use what AttributeRouting calls Route Conventions to create routes automatically for methods, whether inherited from the GenericEditController or implemented in a Controller itself.

public abstract class GenericEditController<TKey, TEntity> : Controller where TEntity : class
{
    public virtual JsonResult Index() { ... }
    public virtual JsonResult Get(TKey id) { ... }
    public virtual JsonResult Delete(TKey id) { ... }
    public virtual JsonResult Update(TKey id, TEntity newEntity) { ... }
    public virtual JsonResult AttributeOptions(TKey id, string attributeName) { ... }
    public virtual JsonResult SetAttributeValue(TKey id, string attributeName) { ... }
    public virtual Tuple<object,object> SetAttributeValueHelper(string attributeName, object value, TEntity obj) { ... } // Used by SetAttributeValue but overrideable on its own, as well
    public virtual JsonResult AttributeCollectionAddNew(TKey id, string attributeName) { ... }
    public virtual T ReadStreamValue<T>() { ... }
    public abstract IRepository GetRepository();
}

Index

This is a convention-based method that responds to the GET /url-root/ call, that returns all entities.

Get

This is a convention-based method that responds to the GET /url-root/id call, that returns a specific entity.

Delete

This is a convention-based method that responds to the DELETE /url-root/id call, that deletes a specific entity.

Update

This is a convention-based method that responds to the PUT /url-root/id call, that updates a specific entity wholesale.

AttributeOptions

This is a convention-based method to respond to the OPTIONS call. It handles two cases.

  • When the property mentioned is a domain entity, in which case is fetches a list of available entities from the Repository.
  • When the property mentioned is an Enum, in which case it returns the list of available values. Integer properties which correspond to Enums are annotated with the UnderlyingEnumAttribute, as in the following example.
    [UnderlyingEnum(typeof(SpGw.Enums.PurchasePlanPaymentType))]
    public virtual int PurchasePlanPaymentTypeID { get; set; }

The Controller can inherit from a number of instantiations of the following interface.

public interface IQueryableRestrictor<T, TEntity>
{
    Func<IQueryable<T>, IQueryable<T>> Restrict(TEntity baseObject);
}

In that case, the appropriate Restrict method will be called to specialize the IQueryable computed by the Repository for the specific base entity involved in the call.

The call returns a JSON object of the form { "id1" : { ...JSON rendering... }, "id2" : { ...JSON rendering... }, ... }. Each entity can be called upon to return its own id by virtue of it implementing the Identifiable interface. This might be lifted in later versions, and the responsibility passed to the Repository directly.

SetAttributeValue

This helper method responds to the setAttributeValue model method, converting as needed. It basically calls SetAttributeValueHelper to do that, so it is the latter method that one should override to perform custom conversions etc.

AttributeCollectionAddNew

This helper method responds to the pushAttributeValue model method. The Controller can inherit from a number of instantiations of the following interface.

public interface IConstructorSpecializer&amp;lt;T, TEntity&amp;gt; where TEntity: class where T:class
{
    T Construct(TEntity baseObject);
}

In that case, the appropriate Construct method will be called to create the new collection element for the specific base entity involved in the call.

GetRepository

This method returns an IRepository, which abstracts away the data layer and provides many helpful generic methods which are called by reflection to make the magick happen. It will be
exposed in a later section.

ReadStreamValue

It is simply a helper method to call JsonHelper.ReadJsonValue<T> on the request stream.

The Repository

The Repository represents the data layer of the application. I had very little to do with its innards (George was responsible for it and I hope he’ll blog about it himself) but I present it here because it’s required by Fundamentum. It enables the GenericEditController and any application Controller to perform data operations. The Repository implements interface IRepository.

    public interface IRepository
    {
        T Load<T>(object id) where T : class; //gets an object without trip to the database
        T Get<T>(object id) where T : class;
        T SaveOrUpdate<T>(T entity) where T : class;
        void Delete<T>(T entity) where T : class;
        IQueryable<T> GetAll<T>() where T : class;
        PropertyInfo FindPrimaryKeyProperty<T>() where T : class;
        T CreateEntity<T>(object id, PropertyInfo property)  where T : class;
        object ConvertedPrimaryKey<T>(object id) where T : class;
    }

It also has the following helper method, as described in the section about property options.

    List<T> ToListRestrictable<T, TEntity>(object r, TEntity baseObject) where T : class { ... }

Currently, the application using Fundamentum implements two different data layers, one with NHibernate and one with EntityFramework, to evaluate the pros and cons of each in order to arrive at the best of them.

Don’t drink the Kool-Aid

I know it’s a beverage popular with developers working with Microsoft technologies. Don’t be afraid to give your own answers and, particularly, don’t be afraid to ask your own questions. You don’t have to believe anything just because an authority says so. Remember the dictum by Isaac Newton, “If I have seen further, it is by standing on the shoulders of giants”? Don’t do the converse, epitomized in the motto in Ketil Malde’s signature:

“If I haven’t seen further, it is by standing in the footprints of giants”.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s