Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Architecture guide: ASP.NET, MVC3, Entity Framework, Code-First with existing database, and a few more..

4.72/5 (27 votes)
11 Dec 2013CPOL12 min read 155.8K   14.2K  
Developing a simple MVC 3 application architecture using Entity Framework’s Code First technology.

Recommended framework: https://nidoframework.codeplex.com/[^]

Introduction

In this whitepaper, I will walk you through developing a simple MVC 3 application architecture using Entity Framework’s Code First technology. Later hopefully you can evolve this to be an architecture that you can use for small to mid-size web application development.

Try to read this from start to end and that way you will be able to gradually build the architecture together with me.

Pre-Requisites

  • Visual Studio 2010
  • SQL Server 2005/ 2008
  • Entity Framework
  • MVC 3
  • Create the database

Create the database

In a real world scenario, the Code-First technique does not encourage you to create a database at all. When you code the models, the system creates the respective database for you. However it does have its own issues, and out of the issue list, the most difficult to live with is, it re-creates the whole database every time the DB schema changes. This makes you lose the existing data in the database. I don't want to deal with that hassle, hence as a work-around we will be taking a different approach. We use the Code-First technique, but with an existing database. As the first step of this, unlike with regular Code First technique, you have to create the database. What you see below is a simple database I have created for me to do this architecture demonstration.

327945/1._Database.PNG

In general when you create a database, the following few best practices are good to follow:

  • Let the composite table have its own primary key.
  • Use field IsActive to avoid physical deletion of records from the database. If not you will end up losing vital information when records are deleted.
  • As a habit use the field Name to describe the table record. This can be used as the record descriptor when displaying records to the user.

Create MVC3 Projects

In this demo I will be using ASP.NET MVC3 Web Application, so let’s create a project and name it "MVC3Demo". Let’s also make it a Razor Internet Application.

327945/2._New_Web_Project.PNG

I decided to have a separate project for the business logic code. So let's create a Class Library type project with the name MVC3Demo.Bll.

Examine the Web.Config of the MVC Web Project

The Web.Config of our Web project has a connection string already defined and if you examine that closely, you will notice that the connection string with the name ‘ApplicationServices’ is by default using the SQL-Express database, which is installed with the Visual Studio 2010 itself. This configuration setting will create a database inside the App_Data folder for you. This is where the system stores application services, such as membership service (if you create a new user in the MVC sample project, then it uses the membership service, to be precise ‘AspNetSqlMembershipProvider’, to create those users in the auto-created SQL Express database) related records. However we don’t want to use that database, so let us modify it and also add another connection string for our database that stores our system's data objects.

The modified web configuration file will look like this..

327945/3._WebConfig.PNG

Now, with this change, we add another entry with the name DemoDbContext and you need to use this very same name to name the database context class that we created to communicate with the database via Entity-Framework. This context class has to be created by inheriting the .NET Framework system class DbContext. This way the system auto-looks for a configuration tag with the name of the class that inherits DbContext to find out the connection string details. Additionally in our case we decide to keep the application service setting untouched, so that we have the option of even using a separate database for our role and profile management.

Create Membership Database

You need to use the command named aspnet_regsql to newly create the membership database in our database server. In order to do that, let’s go to Star >> ‘All Programs’ >> ‘Microsoft Visual Studio 2010’ >> ‘Visual Studio Tools’ >> ‘Visual Studio Command Prompt (2010)’ and type 'aspnet_regsql'. This will drive you through a wizard and let you create the ‘AccessControlDemo’ database in for your database.

327945/4._Access_Control_DB.PNG

Run the Project and verify that new users can be registered with the system and you can login to the system with those newly created users.

Install ‘EntityFramework’ Package for your BLL Project

Since our business logic layer is a Class Library type project, you do not have the required reference for it to be the Entity Framework back end. You can use the Library Package Manager, if you have it (it installs automatically with MVC 3.0), to install the required 'Entity-Framework DLL' for this project. In order to do that, from within your project in Visual Studio 2010, go to Tools > Library Package Manager > Package Manager Console. In the console, after the "PM>" prompt, type "install-package entityframework", and this will install the package and add the ‘EntityFramework’ reference to your WEB project. Use the Installed location and reference EntityFramework.Dll to your BLL project.

Create the DemoDbContext Class

As stated a few times before, this has to be created by inheriting the DbContext class.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;

namespace MVC3Demo.Bll
{
    public class DemoDbContext : DbContext
    {
    }
}

Once this is done, you have almost completed the design framework for your project. The remainder is all about building your system on top of this established framework.

Create Model Classes

These model classes are pretty much like the model classes that you have in the MVC web project itself. However in this case, we must create them to tally with our database tables. There has to have separate model classes for each table and there has to have properties of the same type as fields of the tables. This separation will allow me to share my Models and model access logic across many other projects too.

When you develop by this technique, you need to be extra careful when naming models and their respective properties. One misspell record will break the whole system, and to make it worse, the system generated errors around this region are not a help at all.

So having that in mind, let me creates a class with the name Student. Use 'Copy' and 'Paste' as much as possible to avoid typos. I used to copy the table records like this and..

327945/5._Copy_Table_Records.PNG

paste them on to my code like this..

327945/5._Copy_Code_Records.PNG

and then modify it to be like this…

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;

namespace MVC3Demo.Bll.Models
{
    [Table("Student", Schema = "dbo")]
    public class Student
    {
        [ScaffoldColumn(false)]
        public int StudentId { get; set; }
        [Required]
        [StringLength(50, ErrorMessage="{0} cannot exceed {1} characters")]
        public string Name { get; set; }
        [Required]
        public int Age { get; set; }
        [Required]
        [StringLength(150)]
        public string Address { get; set; }
        [Required]
        [DisplayName("Registered Date")]
        public DateTime RegisteredDate { get; set; }
        [NotMapped]
        public bool SeniorStudent
        {
            get
            {
                DateTime today = DateTime.Now;
                TimeSpan tSpan = today.Subtract(RegisteredDate);
                return (tSpan.TotalDays > 365);
            }
        }
        [Required]
        [DisplayName("Is Active")]
        public bool IsActive { get; set; }
        public string Description { get; set; }
    }
}

Here is an important point, I have used attributes to include validation logic. Not only that, you can have the respective error messages defined and associate them with the property. In addition to that, you have the option of having new properties that are not in the database defined inside the class with a special attribute (see the NotMapped attribute) as well. The property named SeniorStudent is one such record. In that case you need to mark that with the NotMapped attribute. To find out more about property attributes, please look at this link: MSDN.

Just by following the same pattern you can create model classes for the other two tables as well, that is for the tables ‘Course’ and ‘StudentCourse’.

Educate the Code to Relate Related Classes

Now, you may wonder how the relationship in-between tables have been defined here. That is pretty easy, you can educate the model to maintain its relations like this..

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MVC3Demo.Bll.Models
{
    public class StudentCourse
    {
        public int StudentCourseId { get; set; }
        public int CourseId { get; set; }
        public Course Course { get; set; }
        public int StudentId { get; set; }
        public Student Student { get; set; }
    }
}
  • Since StudentCourse has a many-to-one relationship with both the Student and Course classes, you need to define Student and Course objects inside the StudentCourse class. Tip: Look for Id type properties and as a general practice, add a property with the same name right below it.
  • Go to the Student and Course classes and add a property of type ICollection<entity> to indicate the many sides of the one-to-many relationship.

See how the Course class looks now..

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MVC3Demo.Bll.Models
{
    public class Course
    {
        public int CourseId { get; set; }
        public string Name { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public int Period { get; set; }
        public bool IsWeekEnd { get; set; }
        public bool IsActive { get; set; }
        public ICollection<StudentCourse> StudentCourses { get; set; }
    }
}

Note: We have used this technique to develop a few systems in some of the places I worked for and some of these systems are now run in production environments. In development stage, when naming the properties, we always recommended copy and paste of the name as a small typo often leads us to errors that are very difficult to track. However this technique is proven to be good for architecting small to mid-size systems.

Create Controllers

Just compile Mvc3Demo.Bll and add its reference to the main web project. Now compile the web-project, as when not compiled, the Add Controller wizard does not show the modification done in the referenced projects. (I believe Microsoft is aggressively working on some of these minor issues that we see in this version of the release). Then, right click on the controller and fill the respective fields to create the ‘Controller’ and the associated ‘Views’ for our models with names Student, Course, and StudentCourse.

327945/6._Create_Controller.PNG

Compile it and see if everything works. There is one more final adjustment needed. If you run this project as is and try to create a new Course, then you will get an "Invalid object name 'dbo.Courses'" error message. This is because the names of the tables in our database are singular but the context has plural names for the respective lists that directly match with the tables. To avoid this you need to modify the DemoDbContext class as given below.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using MVC3Demo.Bll.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace MVC3Demo.Bll
{
    public class DemoDbContext : DbContext
    {
        public DbSet<Student> Students { get; set; }

        public DbSet<course /> Courses { get; set; }

        public DbSet<studentcourse /> StudentCourses { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<pluralizingtablenameconvention />();
        }
    }
}

Go to the Global.asax file and modify the register route method to point the system to the 'Student' Index page.

327945/7._Route_Register.PNG

Then run the project and see if everything is all right..

327945/8._Screen_Shot.PNG

Let’s now look at the ‘StudentCourse’ create view and see how the required dropdown box is being automatically created..

327945/8._Screen_Shot_Create.PNG

Now with this, we have completed developing a basic system. However if you look at the three controller classes, you will see that there is duplicate code being written. To improve the overall quality of the code, I decided to add an abstract controller class. That way I can avoid duplicate code, and have a better architecture in place for the system. This way I can allow newly joining developers to easily adjust to the code and write high-quality code with very little learning curve.

Create an Abstract Controller Class

In the process of creating this class, I thought of doing a few more additional refactoring as noted below..

  • Add a separate folder called Common, and add a few classes that you may want to commonly use across any project you do to this folder. This includes exception logging, handling of cookies, handling of web.config's application settings data, and a class to have all commonly used strings.
  • In addition to this, I decided to add two extensions to the same Common folder. I hope that may come handy at a later part of this code. One extension is for the Query class and another is added for the View class.
  • Note: As you develop multiple projects, the classes you find in this folder can easily be grown to a separate project itself. Such projects can be grouped under your company name. As an example, if your company name is XYZ, then the common project can be named as ‘Xyz.Common’ and let all the projects your company develop reference this project. In this way you can set a companywide standard for areas in which such standards are applicable.

  • Add a separate folder called Libraries. This will keep all third party DLLs that are being referenced by this project. This way you can avoid some of the common DLL reference related errors that you get after deploying a project into the production environment.
  • Add another class called ConfirmationViewModel to Web projects Model folder. This is just to demonstrate how to call a shared view for the deletion of records. You will see this being used later..

With these refactoring, I managed to remove almost all the duplicated code from my controller class and implement them in my generic abstract class. Finally, the fully re-furbished CourseController class looks like this..

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVC3Demo.Bll.Models;
using MVC3Demo.Bll;

namespace Mvc3Demo.Controllers
{
    public class CourseController : BaseController<Course>
    {
        public CourseController()
            : this(new DemoDbContext())
        { }

        public CourseController(DemoDbContext db)
            : base(db.Courses, db)
        {
        }

        protected override void LoadEditViewBags(Course entity)
        {
        }

        protected override void LoadAddViewBags()
        {
        }

        protected override Type LogPrefix
        {
            get { return this.GetType(); }
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

The important base controller is given below. A part of this code is taken from one of our internal company projects. Hence it has a few additional commonly used methods that are being implemented. I have no issues even if you all decide to use this code in your projects.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVC3Demo.Bll;
using System.Data.Entity;
using System.Data;
using MvcContrib.UI.Grid;
using log4net;
using System.Reflection;
using System.Data.Entity.Infrastructure;
using Mvc3Demo.Common;
using Mvc3Demo.Models;

namespace Mvc3Demo.Controllers
{
    public abstract class BaseController<E> : Controller
        where E : class
    {
        protected DbSet<e /> dbEntitySet;
        protected DemoDbContext db;
        protected readonly ILog logManager;

        public BaseController()
        { }

        public BaseController(DbSet<e /> dbSet, DemoDbContext dbContext)
        {
            dbEntitySet = dbSet;
            db = dbContext;
            logManager = LogManager.GetLogger(LogPrefix);
        }

        public BaseController(DemoDbContext dbContext)
        {
            dbEntitySet = null;
            db = dbContext;
            logManager = LogManager.GetLogger(LogPrefix);
        }

        public virtual ViewResult Index()
        {
            return View(dbEntitySet);
        }

        //
        // GET: /entity/Details/5
        /// <summary />
        /// Get record of the entity from the database 
        /// and pass it on to the Details 'PartialView'
        /// </summary />
        /// <param name="id" />Primary Key of the record to be searched</param />
        /// <returns /></returns />
        public virtual PartialViewResult Details(int id)
        {
            return PartialView(GetDetailsData(id));
        }

        //
        // GET: /entity/Create
        /// <summary />
        /// Create the empty View of the entity
        /// </summary />
        /// <returns /></returns />
        public virtual ActionResult Create()
        {
            GetCreateData();
            return View();
        }

        //
        // POST: /entity/Create
        /// <summary />
        /// newly create the entity object in the database. calls to the 'GetCreatePostBackData' 
        /// and then call to the 'LoadEditViewBags' and fianlly return a 'View'
        /// </summary />
        /// <param name="entity" /></param />
        /// <param name="DoNotRedirect" /></param />
        /// <returns /></returns />
        [HttpPost]
        public virtual ActionResult Create(E entity, bool? DoNotRedirect)
        {
            try
            {
                int i = GetCreatePostBackData(entity, DoNotRedirect);
                if (i < 0)
                {
                    LoadEditViewBags(entity);
                    return View(entity);
                }
                else
                    return Redirect(PortalCookie.Get(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO));
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        // GET: /entity/Delete/5
        /// <summary>
        /// Delete a record via Ajax delete popup
        /// </summary>
        /// <param name="id" />
        /// <returns>
        public virtual ActionResult Delete(int id)
        {
            try
            {
                PortalCookie.Set(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO, Request.UrlReferrer.ToString());
                ConfirmationViewModel confirm = new ConfirmationViewModel();
                confirm.Id = id;
                confirm.Action = "Delete";
                confirm.Controller = typeof(E).Name;
                confirm.OperationName = "Delete";
                if (Request.IsAjaxRequest())
                    return PartialView("Confirmation", confirm);
                else
                    return View("Confirmation", confirm);
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        //
        // POST: /entity/Delete/5
        /// <summary>
        /// Once a delete is confirmed via the popup it will do the DB operation 
        /// and relavent messages added to the view bag
        /// </summary>
        /// <param name="id" />
        /// <returns>
        [HttpPost, ActionName("Delete")]
        public virtual ActionResult DeleteConfirmed(int id)
        {
            try
            {
                E entity = dbEntitySet.Find(id);
                dbEntitySet.Remove(entity);
                int i = db.SaveChanges();
                this.AddToMessagePlus(i + " record(s) Deleted Successfully!"
                    , MessageTypes.Success);
                return Redirect(PortalCookie.Get(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO));
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        //
        // GET: /entity/Edit/5
        /// <summary>
        /// Update a particular entity. 
        /// </summary>
        /// <param name="id" />
        /// <returns>
        public virtual ActionResult Edit(int id)
        {
            try
            {
                E entity = GetEditData(id);
                return View(entity);
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        //
        // POST: /entity/Edit/5

        [HttpPost]
        public virtual ActionResult Edit(E entity, bool? DoNotRedirect)
        {
            try
            {
                int i = GetEditPostBackData(entity, DoNotRedirect);

                if (i < 0)
                {
                    LoadEditViewBags(entity);
                    return View(entity);
                }
                else
                    return Redirect(PortalCookie.Get(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO));
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        protected int EditEntity(E entity)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    UpdateTrackingData(entity, false);
                    db.Entry(entity).State = EntityState.Modified;
                    int i = db.SaveChanges();
                    return i;
                }
                return -2;
            }
            catch (Exception e)
            {
                logManager.Error("Error in Controller", e);
                return -1;
            }
        }

        protected int CreateEntity(E entity)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    UpdateTrackingData(entity, true);
                    dbEntitySet.Add(entity);
                    int i = db.SaveChanges();
                    return i;
                }
                return -2;
            }
            catch (Exception e)
            {
                this.logManager.Error("Db Related Error Occured", e);
                return -1;
            }
        }

        protected IEnumerable<e> GetIndexData()
        {
            try
            {
                return UpdateIncludes(dbEntitySet).AsEnumerable();
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        protected E GetDetailsData(int id)
        {
            try
            {
                return dbEntitySet.Find(id);
            }
            catch (Exception e)
            {
                this.AddToMessage(e.Message, MessageTypes.Error);
                logManager.Error("Error in Controller", e);
                throw e;
            }
        }

        protected void GetCreateData()
        {
            PortalCookie.Set(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO, (Request.UrlReferrer == null)
                ? Request.RawUrl.ToString() : Request.UrlReferrer.ToString());
            LoadAddViewBags();
        }

        protected E GetEditData(int id)
        {
            PortalCookie.Set(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO, Request.UrlReferrer.ToString());
            E entity = dbEntitySet.Find(id);
            LoadEditViewBags(entity);
            return entity;
        }

        /// <summary>
        /// CreateEntity is called and 
        /// then relavent message is add to the view bag
        /// </summary>
        /// <param name="entity" />
        /// <param name="DoNotRedirect" />
        /// <returns>
        protected int GetCreatePostBackData(E entity, bool? DoNotRedirect)
        {
            int i = CreateEntity(entity);
            return AddCreateSatusMessage(DoNotRedirect, i);
        }

        protected int AddCreateSatusMessage(bool? DoNotRedirect, int i)
        {
            if (i == ConstString.ERROR_INT)
                this.AddToMessage("Error: No record(s) Created!", MessageTypes.Error);
            else if (i == ConstString.ERROR_INVALID_OBJECT_INT)
                this.AddToMessage("Warning: Some record(s) yet to be filled!", MessageTypes.Warning);
            else if ((DoNotRedirect.HasValue) && (DoNotRedirect.Value))
                this.AddToMessage(i + " record(s) Created Successfully!", MessageTypes.Success);
            else
                this.AddToMessagePlus(i + " record(s) Created Successfully!", MessageTypes.Success);
            return i;
        }

        /// <summary>
        /// EditEntity is called and 
        /// then relavent message is add to the view bag
        /// </summary>
        /// <param name="entity" />
        /// <param name="DoNotRedirect" />
        /// <returns>
        protected int GetEditPostBackData(E entity, bool? DoNotRedirect)
        {
            int i = EditEntity(entity);
            return AddEditStatusMessage(DoNotRedirect, i);
        }

        protected int AddEditStatusMessage(bool? DoNotRedirect, int i)
        {
            if (i == ConstString.ERROR_INT)
                this.AddToMessage("Error: No record(s) Updated!", MessageTypes.Error);
            else if (i == ConstString.ERROR_INVALID_OBJECT_INT)
                this.AddToMessage("Warning: Some record(s) yet to be filled!", MessageTypes.Warning);
            else if ((DoNotRedirect.HasValue) && (DoNotRedirect.Value))
                this.AddToMessage(i + " record(s) Updated Successfully!", MessageTypes.Success);
            else
                this.AddToMessagePlus(i + " record(s) Updated Successfully!", MessageTypes.Success);
            return i;
        }

        private PagedViewModel<e> createPageViewModel(GridSortOptions gridSortOptions
            , int? page, string sortColumn)
        {
            ShowCurrentMessage();
            return new PagedViewModel<e>
            {
                ViewData = ViewData,
                Query = dbEntitySet,
                GridSortOptions = gridSortOptions,
                DefaultSortColumn = sortColumn,
                Page = page,
                PageSize = Common.ConstString.PAGE_SIZE,
            };
        }

        private PagedViewModel<e> createPageViewModel(GridSortOptions gridSortOptions
            , int? page, string sortColumn, DbQuery<e> dbQuery)
        {
            ShowCurrentMessage();
            return new PagedViewModel<e>
            {
                ViewData = ViewData,
                Query = dbQuery,
                GridSortOptions = gridSortOptions,
                DefaultSortColumn = sortColumn,
                Page = page,
                PageSize = Common.ConstString.PAGE_SIZE,
            };
        }

        private void UpdateTrackingData(E entity, bool isNew)
        {
            this.UpdateTrackingData<e>(entity, isNew);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name" />Name of the view
        /// <param name="value" />Select list to be assigned to a drop down box
        /// <returns>BaseController itself</returns>
        internal BaseController<e> AddToViewBag(string name, SelectList value)
        {
            ViewData[name] = value;
            return this;
        }

        /// <summary>
        /// If you have any dropdown boxes please use this method to load their list
        /// to respective view bags.
        /// </summary>
        /// <example>
        /// this.AddToViewBag("EmployeeId", new SelectList(db.Employees, "EmployeeId", "EPFNo"))
        /// .AddToViewBag("DowntimeReasonId", new SelectList(db.DowntimeReasons, "DowntimeReasonId", "Name"))
        /// </example>
        protected abstract void LoadAddViewBags();

        protected abstract void LoadEditViewBags(E entity);

        protected virtual IQueryable<e> UpdateIncludes(DbSet<e> dbEntitySet)
        {
            return dbEntitySet.AsQueryable<e>();
        }

        protected void UpdateTrackingData<t>(T entity, bool isNew)
        {
            PropertyInfo pInfoMBy = entity.GetType().GetProperty(ConstString.PROPERTY_MODIFY_BY);
            if (pInfoMBy != null)
                pInfoMBy.SetValue(entity, Convert.ChangeType(User.Identity.Name, pInfoMBy.PropertyType), null);
            PropertyInfo pInfoMDate = entity.GetType().GetProperty(ConstString.PROPERTY_MODIFY_DATE);
            if (pInfoMDate != null)
                pInfoMDate.SetValue(entity, Convert.ChangeType(DateTime.Now, pInfoMDate.PropertyType), null);
            PropertyInfo pInfoABy = entity.GetType().GetProperty(ConstString.PROPERTY_ADDED_DATE);
            if (pInfoABy != null)
                pInfoABy.SetValue(entity, Convert.ChangeType(DateTime.Now, pInfoABy.PropertyType), null);
        }

        /// <summary>
        /// Return this.GetType(); This name is used to trace
        /// error locations of the log message write via log4net library.
        /// </summary>
        protected abstract Type LogPrefix
        {
            get;
        }

        public class CommonMessage
        {
            public CommonMessage()
            {
                this.Message = "";
                this.MessageType = MessageTypes.None;
            }

            public CommonMessage(string message, MessageTypes mType)
            {
                this.Message = message;
                this.MessageType = mType;
            }
            public string Message { get; set; }
            public MessageTypes MessageType { get; set; }
        }

        public CommonMessage GetFromMessagePlus()
        {
            string[] tempArray = TempData.GetMessageSummary();
            if (tempArray == null)
                return null;
            if (tempArray.Length > 0)
            {
                string[] mesgs = tempArray[0].Split('|');
                int t;
                if (int.TryParse(mesgs[1], out t))
                    return new CommonMessage(mesgs[0], (MessageTypes)t);
            }
            return null;
        }

        public void AddToMessagePlus(string message, MessageTypes mtype)
        {
            TempData.AddStatusMessage(message + "|" + (int)mtype);
        }

        public void ShowCurrentMessage()
        {
            CommonMessage temp = GetFromMessagePlus();
            if (temp != null)
                AddToMessage(temp.Message, temp.MessageType);
        }

        public void AddToMessage(string message, MessageTypes mtype)
        {
            switch (mtype)
            {
                case MessageTypes.Success:
                    {
                        ViewBag.successbox = message;
                    } break;
                case MessageTypes.Warning:
                    {
                        ViewBag.warningbox = message;
                    } break;
                case MessageTypes.Error:
                    {
                        ViewBag.errormsgbox = message;
                    } break;
            }
        }
    }

    public enum MessageTypes
    {
        Error,
        Warning,
        Success,
        None
    }
}

You need to study this class and see how this can be further improved. Unlike in some of my other articles, I am not going to explain each line of this class, but if any of you have any specific questions, please do post them as a question type comment to this article and I will get back to you with my answer ASAP.

Make the Grid Sortable

There are many fancy grid views around that can be used with MVC2 Razor views. Yes, as you all know, that area needs lot more improvements too (over to MSFT). In my view point, out of the lot, the jQuery grid is the best but I thought of going with a more basic simpler ‘MvcContrib.Gird’ for this..

You can find out more about how to integrate the ‘MVCContrib’ grid here: http://mvccontrib.codeplex.com/wikipage?title=Grid.

In the process of integrating the grid, I had to remove the Index method (Action) implementation from my abstract class. That created ambiguity between that and the new method that I wanted in the CourseController class. So if I am going with the MVCContrib grid, I have two possible design options:

  • Remove the Index method implementation from the abstract base controller and have that implemented in the specific controller classes.
  • Have a new View defined to do the action implementation for it in the specific controller while preserving the Index method of the abstract controller class.

I decided to go with the first option as that makes my design more simpler and provides relatively less options to choose Smile | :). There is a famous theory in software design, ‘it is not about adding but removing all unwanted’. Let's practice that..

This is how the newly modified Course's Index ‘View’ looks like..

327945/8._Index_COde.PNG

The final ‘Course’ Index view looks like this..

327945/9._Final_Screen.png

Next Recommended Read

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)