Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Enterprise Asp.Net MVC Part 2: Building The Model

Posted on: 2012-10-26

This is part 2 of the enterprise Asp.Net MVC web application creation. We have before discussed about the project that we will develop and now we will work on the model. We will create all classes first.

If we remember the UML class diagram, we will have to create 5 classes. One for the Workout, one the for WorkoutSession, one for the Exercise, one for the Muscle and one for the MuscleGroup.

All these classes will be used to contain the business logic but also to contains the Entity from Entity Framework 5. Since we are building in Code First mode with Entity Framework, we have to develop all business logic (model) classes first then the database will be generated by Entity Framework ORM.

So far, if I translate the Model diagram into classes I have :

public class BaseModel { 
  public int Id { get; set; } 
}

public class Workout:BaseModel { 
  public DateTime StartTime { get; set; } 
  public DateTime EndTime { get; set; } 
  public string Name { get; set; } 
  public string Goal{ get; set; } 
  public ICollection<WorkoutSession> Sessions { get; set; } 
}

public class WorkoutSession:BaseModel { 
  public string Name { get; set; } 
  public ICollection<Exercise> Exercises { get; set; } 
}

public class Exercise:BaseModel { 
  public string Name { get; set; } 
  public string Repetitions { get; set; } 
  public string Weights { get; set; } 
  public string Tempo { get; set; } 
  public TimeSpan RestBetweenSet{ get; set; } 
  public virtual Muscle Muscle { get; set; } 
  public ICollection<WorkoutSession> WorkoutSessions { get; set; } 
}

public class Muscle : BaseModel { 
  public string Name { get; set; 
} 

public virtual MuscleGroup Group { get; set; } 
public ICollection<Exercise> Exercises { get; set; } }

public class MuscleGroup:BaseModel { 
  public string Name { get; set; } 
  public ICollection<Muscle> Muscles { get; set; } 
} 

Indeed, I separate all these classes into individual file. Few modeling problem raised on my mind while I was writing those classes. First, every exercise need to be sorted for the user because every exercise are always done in a specific order. We need to add an Order property but we cannot add it to the Exercise class because the order will change depending of the workout session. For example, I may have the "Bicep Curl" exercise done in first on Monday and last on Friday. Also, we will create a directory of Exercise later so we need to have the "meta data" of exercise somewhere else from the exercise of the workout session. In fact, if we put out database glasses, this information would be in a junction table. If we put back our developer glasses, we will simply have a WorkouttSessionExecise that will have 1-1 relationship to an Exercise. So, let's modify the model diagram and the class.

The modification need to be reflected into the classes.

public class WorkoutSession:BaseModel { 
  public string Name { get; set; } 
  public ICollection<WorkoutSessionExercise> WorkoutSessionExercises { get; set; } 
  public virtual Workout Workout { get; set; } 
}

public class WorkoutSessionExercise:BaseModel { 
  public int Order { get; set; } 
  public string Repetitions { get; set; } 
  public string Weights { get; set; } 
  public string Tempo { get; set; } 
  public TimeSpan RestBetweenSet { get; set; } 
  public virtual Exercise Exercise { get; set; } 
  public virtual WorkoutSession WorkoutSession { get; set; } 
}

public class Exercise:BaseModel { 
  public string Name { get; set; } 
  public virtual Muscle Muscle { get; set; } 
  public ICollection<WorkoutSessionExercise> WorkoutSessionExercices { get; set; } 
} 

As you can see, if have also moved the repetition, weight, tempo and all specific user/workout session information into something that will let the user add his information while having the Exercise class having the static information like the name of the exercise and the muscle concerned.

The BaseModel class is used to have similar information like the primary key which is an Integer. Later, other information will be added.

Validating the model

The next step is to add validation to the model. We could set it up to the setter of every property where we want to have some validation but we can also use the IValidateObject interface to let the ModelBinding system of Asp.Net MVC handle validation of every model object. If you want more information concerning the IValidationObject, I suggest you to go read this blog post about IValidationObject interface. In short, the ModelBinding will verify every Validate method of the model when bound back to the controller and it will also Validate the object before saving it into Entity Framework. So, we have double validation automatically execute by .Net Framework. This is a big advantage because we cannot forget to validate the model since the framework do it for us. To make it mandatory we will inherit the interface from the BaseModel class and create an abstract method that will be defined on every model. This way, we are sure that we will have model with validation defined.

 public abstract class BaseModel : IValidatableObject { public int Id { get; set; }

#region IValidatableObject Members

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { return ValidateModel(validationContext); }

#endregion

protected abstract IEnumerable<ValidationResult> ValidateModel(ValidationContext validationContext); } 

All our model classes are modified to have the abstract method defined. Here is an example of a model class with validation and one that doesn't have (yet) any validation logic.

public class Workout : BaseModel { 
  public DateTime StartTime { get; set; } 
  public DateTime? EndTime { get; set; } 
  public string Name { get; set; } 
  public string Goal { get; set; } 
  public ICollection<WorkoutSession> Sessions { get; set; }

  protected override IEnumerable<ValidationResult> ValidateModel(ValidationContext validationContext) { 
    if (string.IsNullOrEmpty(Name)) { 
      yield return new ValidationResult("Name is mandatory", new[] {"Name"}); 
    } 
    
    if (EndTime.HasValue) { 
      if (StartTime > EndTime.Value) { 
        yield return new ValidationResult("EndTime must be after the StartTime", new[] {"StartTime", "EndTime"}); 
      } 
    } 
  } 
}

public class WorkoutSessionExercise : BaseModel { 
  public int Order { get; set; } 
  public string Repetitions { get; set; }
   public string Weights { get; set; } 
   public string Tempo { get; set; } 
   public TimeSpan RestBetweenSet { get; set; } 
   public virtual Exercise Exercise { get; set; } 
   public virtual WorkoutSession WorkoutSession { get; set; }

  protected override IEnumerable<ValidationResult> ValidateModel(ValidationContext validationContext) { 
    return new Collection<ValidationResult>(); 
  } 
} 

The first example show you some validation on the Name that need to be defined. It also validate the StartTime that must be before the EndTime. As you can see, the error will be displayed on both property if the error occur.

I also defined the EndTime as Nullable. This will let the user not enter a ending date to the workout, and for us, give us some scenario to test with nullable type.

The second example show you an example where we do not have any validation defined. It returns a simple empty collection (that must be inherit from IEnumerable).

Localized string

The last thing that bother me with this model is that for the moment, we take for grant that everything is in English all the time. In fact, the workout goal could be in the user language but the exercise name must be translated into the language of the user. In a previous blog post, we have discussed about a technique that can be used to have with Entity Framework multiple language handled automatically. It also doesn't brake any object oriented theory. So, let's apply now the modification to the model by changing some string into LocalizedString class.

To do, we will add this class :

[ComplexType] 
public class LocalizedString { 
  public string French { get; set; } 
  public string English { get; set; }

[NotMapped] 
public string Current { 
  get { return (string) LanguageProperty().GetValue(this); } 
  set { LanguageProperty().SetValue(this, value); } 
}

public override string ToString() { return Current; }

private PropertyInfo LanguageProperty() { 
  string currentLanguage = Thread.CurrentThread.CurrentUICulture.DisplayName; 
  return GetType().GetProperty(currentLanguage); 
  } 
} 

This class lets you have French and English for every LocalizedString defined. It will add a column in the database for French and one for English.

As you can see, the LocalizedString does have a ComplexType attribute which will tel Entity Framework to merge the property into the owner and not to create a relationship to a table called LocalizedString. For example, we will use LocalizedString with the name of Exercise. So the Exercise will have in its table Name_French and Name_English.

public class Exercise : BaseModel { 
  public LocalizedString Name { get; set; } 
  public virtual Muscle Muscle { get; set; }
   public ICollection<WorkoutSessionExercise> WorkoutSessionExercices { get; set; }

  protected override IEnumerable<ValidationResult> ValidateModel(ValidationContext validationContext) { 
    if (Name==null) { 
      yield return new ValidationResult("Name is mandatory", new[] {"Name"}); 
    } 
  } 
} 

As you can see, the Name property is now of type LocalizedString and we have modified the validation that now check if the name is defined.

Series Articles