Valknut – View Models

The application utilises view models heavily for all kinds of client-side communication. A page view model is an obvious candidate. But even JSON requests and responses are deserialised into a view model for better server-side manipulation.

Core Hierarchy

The application view model architecture is highly formalised. It follows a strong hierarchy for transmitting the various kinds of data models to the client and back. The base of this entire hierachy is the class FitNet.Web.ViewModels.EntityViewModel. This class exposes the properties Id and Name.

All entity view models which are used to enumerate records, show editable forms or delete rows inherit from EntityViewModel. This offers the structure required to perform common sets of actions on these entities irrespective of their individual types and properties.

Page View Model

This is a generic class that is designed for transmitting data for a standard HTML output. It has a single generic type parameter that has to inherit from the EntityViewModel. This class exposes the properties Title, Permalink and Entity. The type of Entity matches the type of the generic parameter.

All entity controllers can use this class to generate an editor page with a shared set of page properties and layout hosting a form with entity type-specific fields.

DataTables View Models

DataTables is a popular jQuery plugin for rendering grids on the client-side. It supports dynamic retrieval of data, page-wise splitting, record offsets and many other features required for viewing collections.

This library is utilised heavily in Valknut, and as a result, the back-end provides built-in view models for data retrieval and submission directly through DataTables.

The classes required for this feature are listed below.

  • FitNet.Web.ViewModels.DataTables.RequestViewModel
  • FitNet.Web.ViewModels.DataTables.ColumnViewModel
  • FitNet.Web.ViewModels.DataTables.OrderViewModel
  • FitNet.Web.ViewModels.DataTables.SearchViewModel
  • FitNet.Web.ViewModels.DataTables.ResponseViewModel

Request View Models

This is a composite class that organises several other view models into a single instance. When the plugin makes an AJAX request, the request is serialised into this type. It’s native properties are Draw, Start, Length and Error. These properties are required for cache-busting, record navigation and error handling.

The Search property contains an instance of the SearchViewModel, while Columns and Order are collections of type ColumnViewModel and OrderViewModel respectively.

Server-side Processing

An AJAX request from a DataTables instance can be processed on the client-side or on the server. In the former scenario, all data is retrieved in a single network operation and stored in memory on the client. This is convenient and quick for small data sets.

But if your data sets are large or contain an indefinite number of rows, server-side processing is more efficient. For this, the plugin sends some additional information with each request.

  1. Column Data
  2. Column Name
  3. Column Searchable
  4. Column Orderable
  5. Column Search Value
  6. Column Search RegEx
  7. Column to Order By
  8. Direction of Ordering
  9. Record Number to Start At
  10. Length of Data Set
  11. Global Search Value
  12. Global Search RegEx

Column and ordering parameters are an indexed collection. The entire request is URL encoded. An example of the body sent is shown below (with the request split into multiple lines for legibility).

draw=1&
columns[0][data]=Name&
columns[0][name]=&
columns[0][searchable]=true&
columns[0][orderable]=true&
columns[0][search][value]=&
columns[0][search][regex]=false&
columns[1][data]=Id&
columns[1][name]=&
columns[1][searchable]=true&
columns[1][orderable]=false&
columns[1][search][value]=&
columns[1][search][regex]=false&
order[0][column]=0&
order[0][dir]=asc&
start=0&
length=10&
search[value]=&
search[regex]=false

Column View Model

The column fields are deserialised into an instance of this class. Each column has its own instance, and each instance has the properties Data, Name, Searchable, Orderable, Start, Length, as well as a nested instance of the search view model.

The server can utilise these values to perform any additional ordering or filtering on the result data set before dispatching it back to the client.

Order View Model

This is a very simple class consisting of only two properties – Column and Direction. The first is the index of the primary sorting column, and the second is a string containing the value asc or desc.

The server can utilise these properties to sort the data on the server.

Search View Model

This class contains the properties Value and RegEx, which is used to transmit keywords or regular expressions to the server. The data set can be filtered against these parameters before returning it to the client.

Response View Model

In addition to the collection of records, the DataTables instance also requires some additional information such as the current page number. This view model implements properties for these values.

The complete list of properties are Draw, Start, RecordsFiltered, RecordsTotal, Search, Error and Data. The last one is an ICollection instance and contains the rows to be rendered.

Valknut – Validation

Introduction

ASP.NET MVC has always had a robust request validation model that is easily harnessed. The model in question has to be decorated with validation attributes from a broad collection of possibilities, and the framework takes care of ensuring compliance and distilling it into a single boolean that can be referenced from ModelState.IsValid.

Nothing could be easier.

However, this approach requires that the programmer must remember to write the code to perform a validation check in each action method. This is tedious and error-prone, and results in duplicated code.

It is possible to implement this functionality in a more modular fashion by implementing it as a filter attribute and plugging it into the ASP.NET request pipeline.

Passive Attributes

Before we go into the implementation of the filter itself, there is a slight design idiosyncracy that has to be understood. Request filtering through a class that derives from ActionFilterAttribute is a common enough pattern. A request is filtered through this attribute if the method implementing its corresponding action is decorated with this attribute. But this approach imposes various technical restrictions and design compromises.

A more robust approach is to decorate the action method with a non-behavioural attribute that derives from Attribute, and adding the filter directly into the global filter collection.

Passive attributes are described by Mark Seemann at the link below.

Passive Attributes

Implementation

The passive attribute approach results in an attribute class called ValidateModelAttribute. Any method that requires model validation can be decorated with this attribute.

[HttpPost]
[ValidateModel]
public ActionResult Edit(PageViewModel<ExerciseEditViewModel> viewModel)

A RequestValidateModelFilter class implements the IActionFilter interface and is added into the global filters collection during the Application_Start event. When an incoming request arrives, it is passed through the RequestValidateModelFilter instance, which checks if the request requires validation, and if so, checks if the IsValid property of the model state is true.

If the model state is not valid, then the request pipeline is truncated and all validation errors are gathered into a JSON response. The application returns a HTTP status code 400 along with the list of errors in the body of the response.

Valknut – Localisation

Client-specific Regional Settings

Request Structure

Valknut implements localisation of temporal data to match the client’s preferred locale through the use of the Accept-Language header in an incoming request. The value of this header can be an array of one or more language tags, such as en-US, along with a weightage indicator for the language.

This value is set automatically by the browser based on the regional settings of the operating system, and can be overridden by the user to suit their needs. The language tag may be followed by an optional quality factor parameter that indicates the preferred weightage to the given language tag. The value of the q-factor is a decimal number between 0 to 1.

en-US;q=1,en-GB;0.5

The above example indicates that the client prefers American as well as British English, with a higher preference for American English.

An example of an entire Accept-Language header is shown below.

Accept-Language: en, en-GB;q=0.8, hi;q=0.7

This is equivalent to having the value en;q=1,en-GB;q=0.8,hi;q=0.7.

Server-side Extraction

On the server side, the framework parses the header and automatically populates the UserLanguages property of the request object. The type of UserLanguages is string[], and its entries are sorted in descending order by the quality factor. In the example above, the language tag en is the first entry, and hi is the last entry.

Once the request language has been determined, the method checks if it is whitelisted in the allowedLanguages parameter. It first checks if the entire language tag and its subtag are present in the whitelist, or failing that, if the language tag alone is found. That way, if the application only supports French language localisation, but the request contains the tag fr-CA, the response is still localised to the French language, even though it does not contain Canadian region-specific date or number formatting.

In spite of all this, if the method is unable to determine the preferred language from the request, it applies the culture that is currently active on the operating system.

Request Handling

The server matches the current culture of the thread fulfilling the request to a value from this array in order to process the request in a culture-aware manner. Currently this extends to temporal data, with scope for future support for UI string localisation, formatting of numerical data, units of measurement and iconography.

The code to configure this for the current thread is written in an extension method on the System.Web.HttpApplication class.

void SetLocale(this HttpApplication application, 
    string culture = null, 
    string uiCulture = null, 
    bool setUiCulture = true, 
    string allowedLocales = null)

This method is invoked from the Application_BeginRequest event handler, because the locale has to be set separately for each request. The SetLocale method is called directly from this event handler. The method is invoked without any parameters, which indicates that the preferred locale has to be inferred from the request headers.

If the Accept-Language header is not present in the request, the method falls back upon the regional settings configured on the server to handle the request.

The Thread class has two properties to set the culture.

  1. CurrentCulture, which determines the writing system (LTR or RTL), calendar, string sorting and formatting of date and time strings.
  2. CurrentUICulture, which determines the resource file used by the Resource Manager to localise the strings, icons and other non-code assets for the application.

This separation allows applications to be able to handle regional settings for data even if it does not support region-specific UI labels. The SetLocale method sets both properties to the same culture unless the value of setUiCulture is false.

Valknut – Domain Model

The application centres around log entries. Every time you train, you create a log entry. The log entry stores the time when the training session was started and completed. The log entry contains a collection of sets. A set consists of one or more repetitions of a single exercise at a uniform intensity.

All other entities in the domain model projects out from this central requirement.

A set consists of an exercise and repetitions. Exercises are categorised by muscle groups. Hence there are exercises and categories.

Sequences of sets are organised into a routine, which serve as a template for a training log.

  1. Categories – A list of body muscle groups, which is used to classify exercises.
  2. Exercises – A list of body activities for improved strength, conditioning, agility or endurance.
  3. Repetitions – The number of times a complete motion of an exercise has to be executed.
  4. Sets – A group of continuous, consecutive repetitions of the same exercise.
  5. Routines – A collection of several sets of one or more exercises, to be performed sequentially.
  6. Training Logs – A record of the date, time and duration when a particular routine was performed.

Categories

Categories muscle groups in the body. This list is static and has no user-interface elements to modify its contents. It is created when the database for the application is initially created, and remains unchanged throughout its lifetime. The full list of categories included in Valknut is given below.

  1. Abdominals
  2. Abductors
  3. Adductors
  4. Biceps
  5. Body
  6. Calves
  7. Chest
  8. Forearms
  9. Glutes
  10. Hamstrings
  11. Lats
  12. Lower Back
  13. Middle Back
  14. Neck
  15. Quadriceps
  16. Shoulders
  17. Traps
  18. Triceps

Some fitness-training literature uses the term category to describe the the primary goal of the exercise. These systems consist of strength, hypertrophy, endurance, flexibility, balance, agility, speed, power and accuracy. This aspect is (partially) fulfilled in Valknut by the exercise property called type.

Exercises

An exercise has a name, a category and a type. Categories and types have been explained above. New exercises can be created at any time, but to delete an exercise, its entries from the routines and log entries must be remove manually beforehand.

Routines

A routine is a sequence template. It describes the desired initial structure of a training session as a series of exercises to be performed, the number of sets for each exercise, the intensity of the set, and the number of repetitions to be performed. Weights and repetitions for each set can differ in order to create straight, pyramid or reverse-pyramid patterns.

Training Log

The training log is a memo of the name of the workout, the sequence, intensity and repetitions of its component exercises, the date and time when it was performed and the duration it took.

Routines serve as a template for a log entry. When a new entry is added to the training log, the sequence of exercises, the number of sets, the intensity of the exercise and the number of repetitions are copied from a previously created routine. But log entries do not retain any association with the routine. If the routine is modified after a log entry has been created, its modifications do not reflect on the log entry. Conversely, the sequence of exercises and sets in a log entry can be altered without affecting the routine from which it was created.

Valknut – Workout Tracking

Valknut is a workout tracking application. It is designed to keep logs about any kind of weighlifting activity, such as barbell training, bodyweight training or dumbbell workouts.

The original motivation for this project was to serve as a personally tailored replacement to commercial offerings.

History

The initial product idea germinated in the year 2016 as a means to store personal health information safely on an individual desktop computer, away from prying eyes. To that end, the application never considered the possibility of multiple simultaneous users, authentication, or non-local persistence. Each individual user would store their own records in their personal file, which would be protected by the user’s own encryption key. The project was rather unimaginatively titled Fit Net.

After remaining shelved for a very long time, I picked it up again in 2020 to breathe some life back into it. Among other things, I changed it into a web application since that was a platform I knew well by then, and renamed it to a much more distinguished Valknut, invoking imagery of Norse mythology, Viking warriors and Valhalla.

Does a workout even count if you don’t feel at the threshold of Valhalla by the time it’s complete?

Project Status

The product is in what I call functionally usable state. It provides all necessary features to capture, store, retrieve, edit and delete the most essential facets of a weightlifting regimen. A basic summary report has been implemented.

The architecture of the application still aspires to be amended into a desktop-based product someday.

Architecture

Valknut follows classical MVC architecture. The application is separated into three projects for the user interface, the querying engine and the entity model classes. The website project contains classes that implement the web controller interfaces and the views. The querying engine provides repositories, data-error abstractions and query and filtering operations. The models project is a class library to implement the entities that make up the domain model.

The following links expand upon select architectural aspects of the product.

Domain Model

Localisation

Validation

View Models

JSON Handler