Building a LOB application with MVC 5 – Part 2 – Models and Generic Repository

In the previous part of building line of business application with MVC 5, we introduced MVC and created the solution structure

if you didn’t read the first 2 parts, please go ahead and read it from the below links

  1. Building a LOB application with MVC 5 – Part 0
  2. Building a LOB application with MVC 5 – Part 1

In this part, we will introduce Models and build the needed business entities in our business application, we will also explain how to save the data using Entity Framework, below are the exact topics we are going to discuss about models:

  1. Developing Models
  2. Using Display and Edit Data Annotations on Properties
  3. Validating User Input with Data Annotations
  4. What Are Model Binders?
  5. Entity Framework 6

 

Developing Models

Every software project has a model, this model may or may not map exactly to a database, the model is a collection of classes and relationships between these classes, every class has some properties, these properties can be primitives like int, string, Boolean, or it can be complex like Customer, Order ..

In our application, the model will be as below

Models

To add the model to your visual studio solution, add the following classes to the AppointmentManager.Models, after you are done, your models project should look like the following.

NB: I have added the code for main classes only, the rest are very simple and can be added by looking at the class diagram

 

Models Project

  1. BusinessEntity, this is the base class that all model entities will inherit from, it contains some common properties between all classes like ID, Creation date and last modification date, the settings of these properties will be done in one place in our repository to minimize the effort needed
    public abstract class BusinessEntity
        {
            public int ID { get; set; }
    
            public DateTime CreationDate { get; set; }
    
            public DateTime LastModificationDate { get; set; }
    
            public User CreatedBy { get; set; }
    
            public User LastModifiedBy { get; set; }
        }
     
  2. ValueObject, this is the base for all model objects that have no identity, unlike entities that have an ID that distinguish it from other entities, value objects have no ID, it is identified by its value, ex: a Money class has no ID, only the amount identities it, in our case, a TimeSlot has no ID, it can be identified by the start time and duration
    public class ValueObject<T>
        {
            T Value { get; set; }
        }
     
  3. User
    public class User : BusinessEntity
        {
            public bool IsActive { get; set; }
    
            public string FirstName { get; set; }
    
            public string LastName { get; set; }
    
            public string EmailAddress { get; set; }
    
            public string MobileNumber { get; set; }
        }
     
  4. Customer
     public class Customer : User
        {
            public string Address { get; set; }
    
            public virtual ICollection<Appointment> Appointments { get; set; }
    
            public virtual ICollection<CustomerProfile> Profiles { get; set; }
    
        }
     
  5. CustomerProfile
    public class CustomerProfile : BusinessEntity
        {
            public string Name { get; set; }
    
            public string CustomerID { get; set; }
    
            public int ServiceTypeID { get; set; }
    
            public ServiceType ServiceType { get; set; }
    
            public virtual Customer Customer { get; set; }
    
            public virtual ICollection<CustomerProfileAttribute> Attributes { get; set; }
        }
     
  6. Service
    public class Service : BusinessEntity
        {
            public string Name { get; set; }
    
            public string Description { get; set; }
    
            public int ServiceTypeID { get; set; }
    
            public virtual ServiceType ServiceType { get; set; }
        }
     
  7. ServiceProvider
          public class ServiceProvider : User
            {
                public string Title { get; set; }
    
                public string StreetAddress1 { get; set; }
    
                public string StreetAddress2 { get; set; }
    
                public string City { get; set; }
    
                public string State { get; set; }
    
                public string CountryID { get; set; }
    
                public string ZipCode { get; set; }
    
                public string BusinessTelephoneNumber { get; set; }
    
                public string WebSite { get; set; }
    
                public string TaxIdentificationNumber { get; set; }
    
                public string LicenseNumber { get; set; }
    
                public bool IsAvailableSaturday { get; set; }
    
                public bool IsAvailableSunday { get; set; }
    
                public bool IsAvailableMonday { get; set; }
    
                public bool IsAvailableTuesday { get; set; }
    
                public bool IsAvailableWednesday { get; set; }
    
                public bool IsAvailableThursday { get; set; }
    
                public bool IsAvailableFriday { get; set; }
    
                public virtual Country Country { get; set; }
    
                public virtual ICollection<Appointment> Appointments { get; set; }
    
                public virtual ICollection<ServiceType> ServiceTypes { get; set; }
    
                public virtual ICollection<TimeSlot> Availability { get; set; }
    
                public virtual ICollection<ServiceProviderReview> Reviews { get; set; }
            }
     
  8. ServiceProviderReview
    public class ServiceProviderReview : BusinessEntity
        {
            public string ServiceProviderID { get; set; }
    
            public int Rating { get; set; }
    
            public int AppointmentId { get; set; }
    
            public string CustomerId { get; set; }
    
            public string Comment { get; set; }
    
            public virtual ServiceProvider ServiceProvider { get; set; }
    
            public virtual Appointment Appointment { get; set; }
    
            public virtual Customer Customer { get; set; }
        }
     
  9. TimeSlot
     public class TimeSlot : ValueObject<TimePeriod>
        {
            public TimePeriod Value
            {
                get; set;
            }
        }
    
        public class TimePeriod
        {
            public DateTime StartTime { get; set; }
    
            public int DurationInMinutes { get; set; }
        }
     
  10. Appointment
    public class Appointment : BusinessEntity
        {
            public int ServiceProviderID { get; set; }
    
            public int CustomerID { get; set; }
    
            public int CustomerProfileId { get; set; }
    
            public int AppointmentStatusId { get; set; }
    
            public int TimeSlotId { get; set; }
    
            public DateTime AppointmentTime { get; set; }
    
            public int DurationInMinutes { get; set; }
    
            public bool IsPaidByProvider { get; set; }
    
            public AppointmentStatus AppointmentStatus { get; set; }
    
            public Customer Customer { get; set; }
    
            public ICollection<Service> Services { get; set; }
    
            public ServiceProvider ServiceProvider { get; set; }
    
            public CustomerProfile CustomerProfile { get; set; }
    
            public TimeSlot TimeSlot { get; set; }
    
            public ServiceProviderReview CustomerReview { get; set; }
        }
    

Now, we defined our model, lets define how will we store the model objects into the database using the Repository layer defined in the AppointmentManager.Repository project, we will be using SQL Server as a persistence medium.

As we know, we should always program against interfaces and also we should not repeat our selves, this is why we will create a generic Repository that will abstract the CRUD operations.

Create a new interface called IRepository in the AppointmentManager.Repository project and add the following code to it.

 public interface IRepository<T, K> where T : class
    {
        T Add(T item);

        bool Update(T item);

        bool DeleteByID(K id);

        bool Delete(T item);

        T GetByID(K id);

        IQueryable<T> GetAll();

        IQueryable<T> GetAll(Expression<Func<T, K>> orderBy);

        IQueryable<T> GetAll(int pageIndex, int pageSize);

        IQueryable<T> GetAll(int pageIndex, int pageSize, Expression<Func<T, K>> orderBy);

        IQueryable<T> Find(Expression<Func<T, bool>> predicate);

        IQueryable<T> Find(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, Expression<Func<T, K>> orderBy);
    }

As you can see, this interface has all the data access logic you may need in most of the business cases, also all methods returns IQueryable which allows the caller to add extra filtering.

As we explained in Part 1, the data access layer will be called by the service/business logic layer , so we need also a generic service layer that will call the data access layer generic repository.

To do that, create a new interface in the AppointmentManager.Services project and name it IService

 
public interface IService<T, K> where T : class
    {
        T Add(T item);

        bool Update(T item);

        bool DeleteByID(K id);

        bool Delete(T item);

        T GetByID(K id);

        IQueryable<T> GetAll();

        IQueryable<T> GetAll(Expression<Func<T, K>> orderBy);

        IQueryable<T> GetAll(int pageIndex, int pageSize);

        IQueryable<T> GetAll(int pageIndex, int pageSize, Expression<Func<T, K>> orderBy);

        IQueryable<T> Find(Expression<Func<T, bool>> predicate);

        IQueryable<T> Find(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, Expression<Func<T, K>> orderBy);
    }
 

Now, in the same project, create a new class and name it BaseService and make it inherits the IService class, make sure to add a reference to AppointmentManager.Repository

public class BaseService<T, K> : IService<T, K> where T : class
    {
        private IRepository<T, K> _repository;

        public BaseService(IRepository<T, K> repository)
        {
            this._repository = repository;
        }

        public T Add(T item)
        {
            return _repository.Add(item);
        }

        public bool Delete(T item)
        {
            return _repository.Delete(item);
        }

        public bool DeleteByID(K id)
        {
            return _repository.DeleteByID(id);
        }

        public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
        {
            return _repository.Find(predicate);
        }

        public IQueryable<T> Find(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, Expression<Func<T, K>> orderBy)
        {
            return _repository.Find(predicate, pageIndex, pageSize, orderBy);
        }

        public IQueryable<T> GetAll()
        {
            return _repository.GetAll();
        }

        public IQueryable<T> GetAll(Expression<Func<T, K>> orderBy)
        {
            return _repository.GetAll(orderBy);
        }

        public IQueryable<T> GetAll(int pageIndex, int pageSize)
        {
            return _repository.GetAll(pageIndex, pageSize);
        }

        public IQueryable<T> GetAll(int pageIndex, int pageSize, Expression<Func<T, K>> orderBy)
        {
            return _repository.GetAll(pageIndex, pageSize, orderBy);
        }

        public T GetByID(K id)
        {
            return _repository.GetByID(id);
        }

        public bool Update(T item)
        {
            return _repository.Update(item);
        }
    }

So far, We have a base service to be used for our business logic, a base interface to be used in our data access layer.
Still we didn’t write any code to save our entities into the database as this will require EntityFramework which will be introduced in the next part.

You can get the full source code from GitHub Repository https://github.com/haitham-shaddad/AppointmentManager