Rohan Büchner
Rohan Büchner

Reputation: 5403

Inheritance/Generics Basics. How to implement a code structure that's DRY

In an attempt to DRY up my code today i'd like to do the following. (I don't know if its the best way, but it seems better than to have an ever increasing code base where I continually need to update multiple methods if i want to change something across the whole site)

What i know about Inheritance is scary. As Iv'e never questioned any of the code/libraries that I use, and Iv'e never really attempted writing anything like this before, but I want to learn... Hoping this will be my day of enlightenment :P

To my question:

Say Iv'e got an add method (in all my controllers) like this:

public ActionResult Add(VM_Down_Time_Capture viewModel)
        {
            using (Down_Time_CaptureRepository repository = new Down_Time_CaptureRepository())
            {
                if (!ModelState.IsValid)
                    return ReturnValidationFailure(ViewData.ModelState.Values);

                Down_Time_Capture model = new Down_Time_Capture();
                model.InjectFrom(viewModel);

                string mserMsg = repository.Add(model, User.Identity.Name);

                if (!string.IsNullOrEmpty(mserMsg))
                    return ReturnCustomValidationFailure(Server.HtmlEncode(mserMsg));

                repository.Save();

                return Json("Added successfully.", JsonRequestBehavior.AllowGet);
            }
        }

And at the moment I've got the following as well.

Generated by T4 Templates/EF.

    ViewModels, Repositories, (Standard) EF Models

I'm thinking I need a ModelSpecfic base controller for each page (can be done using T4), that inherits from a custom ControllerBase class that contains the basic CRUD functionality. That way i can have custom code per controller, and my code base will be cleaner & smaller & that wont get affected should i need to regenerate the base files

I don't quite understand how to implement something in the lines of what i need. What i understand so far is that ill need to have my repositories, and view models inherit from a base as well and somehow specify in [B] which ones I'm using... but as to how to do that i don't know

For example (and this is my best attempt at it, not my actual code, extremely hacky as I'm amazingly confused :S)

    public class Down_Time_CaptureController : Down_Time_CaptureBase
    {
     //[A]
    }

    //Generated by T4
    public class Down_Time_CaptureBase: ControllerBase
    {
      //[B]
       public override EntityObject CreateNewModel()
       {
          return new Down_Time_Capture();
       }

       public override Base_Repository CreateNewRepository()
       {
          return new Down_Time_CaptureRepository();
       }

       public override Base_ViewModel CreateNewViewModel()
       {
          return new VM_Down_Time_Capture();
       }

      //how would i go about specifying which repository & model & view model to use
      //although i expect it to be something to what i did here above
      //and how would i go about calling the new generic add method (but in context of this controller)?

    }

    //coded once
    public abstract class ControllerBase: Controller
    {
        //[C]
        //make abstract so i have to override it
        public abstract Base_Controller CreateNewModel();
        public abstract Base_Controller CreateNewRepository();
        public abstract Base_Controller CreateNewViewModel();

        //I'm assuming my generified add method would go in here  
        public virtual ActionResult Add(Base_ViewModel viewModel)
        {
           using (Base_Repository repository = CreateRepository())
           {
                   if (!ModelState.IsValid)
                       return ReturnValidationFailure(ViewData.ModelState.Values);

                   EntityObject model = CreateNewModel();
                   model.InjectFrom(viewModel);

                   string mserMsg = repository.Add(model, User.Identity.Name);

                   if (!string.IsNullOrEmpty(mserMsg))
                       return ReturnCustomValidationFailure(Server.HtmlEncode(mserMsg));

                   repository.Save();

                   return Json("Added successfully.", JsonRequestBehavior.AllowGet);
            }       
        }
    }

Upvotes: 2

Views: 259

Answers (1)

moribvndvs
moribvndvs

Reputation: 42497

Here's a simple generic interpretation of what you are asking for:

// concrete controller implementation
public class Down_Time_CaptureController: ControllerBase<Down_Time_Capture, VM_Down_Time_Capture, Down_Time_CaptureRepository>
{
}

// generic controller base
public abstract class ControllerBase<TModel, TViewModel, TRepository>: Controller
        where TModel : Base_Model, new()
        where TViewModel : Base_ViewModel, new()
        where TRepository : Base_Repository, new()
{
    protected virtual TModel CreateNewModel()
    {
           return (TModel)Activator.CreateInstance<TModel>();

    }

    protected virtual TRepository CreateNewRepository()
    {
           return (TRepository)Activator.CreateInstance<TRepository>();
    }

    protected virtual TViewModel CreateNewViewModel()
    {
            return (TViewModel)Activator.CreateInstance<TViewModel>();
    }

    //I'm assuming my generified add method would go in here  
    public virtual ActionResult Add(TViewModel viewModel)
    {
       using (var repository = CreateRepository())
       {
               if (!ModelState.IsValid)
                   return ReturnValidationFailure(ViewData.ModelState.Values);

               var model = CreateNewModel();
               model.InjectFrom(viewModel);

               string mserMsg = repository.Add(model, User.Identity.Name);

               if (!string.IsNullOrEmpty(mserMsg))
                   return ReturnCustomValidationFailure(Server.HtmlEncode(mserMsg));

               repository.Save();

               return Json("Added successfully.", JsonRequestBehavior.AllowGet);
        }       
    }
}

A few notes:

  1. You will probably want to create interfaces for the three types (Model, ViewModel, Repository) and use those as the generic constraints.
  2. You will probably want a generic Repository interface and base implementation (so you don't have to code each repository independently, and copy similar logic from one to the other).
  3. Consider using an Inversion of Control container and dependency injection. Rather than have the controller, for example, handle creating an instance of a repository, make it a property and set it from the constructor. You can then use an IoC of your choice (like Ninject or Autofac) and register concrete implementations, and it will manage creating and the lifetime of both the dependencies and the controller itself.

Upvotes: 3

Related Questions