2

I am working on the Design of an .NET MVC WEB application and have determined that I have to Manage 33 tables (so far). By Manage I mean the typical SQL operations (insert,update,delete and query)

16 out of those 33 tables are reference tables that need to be handled with the typical CRUD aproach. By reference table I mean tables that are reference by other tables and used in combos (i.e. coutries, states, cities, currencies, etc)

I am very new to MVC in .NET but have seen that when you scaffold you get Controllers that have the typical methods:

  • Index (get)
  • Details (get)
  • Create (get + post)
  • Edit (get + post)
  • Delete (get + post)

Also on the database side I plan to have the typical DAO that using EntityFramework implements the INSERT, UPDATE, DELETE and LIST (query operations)

Here are my questions (finally ;-)

Since 50% of my tables will be managed visually and functionally in the same way and offer the same CRUD operations is there a pattern or approach that I can use that achieves the best possible code/view reuse ?.

What I want to avoid is to scaffolding 16 entities to get 16 controllers each with 16*5 Views (Create, Delete, Details, Edit and Index)

Is it possible to have a Single Main controller that can route or manage all those 16 entities and calls the entity needed operations ?

Is it possible to have the a single set of Views (Create, Delete, Details, Edit and Index) that are generic enough to handle those same 16 entities ?

If the two above are possible how will I go about wiring the routing in the configuration of the web project to still be able to have specific URLs for each of those 16 entities (/Countries/Edit/5 , /States/Edit/5 and so on) ?

Here is one way that I think I can achieve this:

  • Have an Abstract/Common base class or interface for all those 16 Entities with the logic for the Save, GetAll and Delete database operations that manages the EntityFramework context (i.e. IManageData)
  • Have an EntityDao that using the EntityFramework calls the methods of any class that has implemented the IManageData and performs the operations.
  • Have a Abstract/Common base class (web controller) with the common logic of the CRUD operations of all the 16 entities that can call the methods of the IManageData

Please help me validate/complete my very draft design and mention some caveats or better approaches to achieve this.

Just to provide some context here is a link for the approch of scaffolding EntitieFramework entities with MVC project https://code.msdn.microsoft.com/MVC5-Demo-with-Entity-c6bc81df

Here is a very similar question with an accepted answer that I would like to validate before deciding on how to achieve this.

Generic CRUD controllers and views

4
  • msdn.microsoft.com/en-US/data/ee712907#codefirst. Hope it helps also your question is too broad for SO. Commented Jul 11, 2015 at 12:45
  • @mybirthname I have provided enough details to try to narrow it and sure there are specific implementations of the "Generic Admin" approach. I hope it does not get closed Commented Jul 11, 2015 at 12:47
  • Personally i don't like accepted answer in your link because web code and data code are tightly coupled in that solution Commented Jul 11, 2015 at 13:34
  • @Disappointed how would you go about it then ? Commented Jul 11, 2015 at 13:37

1 Answer 1

5

I had similar situation on my previous project. I did it this way. Here is the general interface for every entity(table)

public interface IRepository<T>
{
    void Add(T entity, User initiator);
    void Update(T entity, User initiator);
    void Delete(T entity);
    void Delete(int entityId);
    IEnumerable<T> GetAll();
    T GetById(int entityId);
}

This general data layer can be easily implemented with Entity Framework(code sample for example here at the bottom) or using different approach(stored procedures as in my case)

I actually don't have any additional business logic, so my web project had direct access to data repository. So it is my generic base controller

public abstract class BaseController<T, M> : Controller
    where M : new()
{
    public BaseController(IRepository<T> repository)
    {
        this._repository = repository;
        ViewBag.CurrentUser = CurrentUser;
    }

    protected User CurrentUser
    {
        get
        {
            if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
            {
                return Mapper.Map<User>(System.Web.HttpContext.Current.User.Identity);
            }
            return null;
        }
    }

    protected virtual int PageSize
    {
        get { return 5; }
    }

    protected IRepository<T> _repository;

    public virtual ActionResult Index(int? currentPage)
    {
        var entities = _repository.GetAll();
        List<M> model = new List<M>();

        foreach (var currentEntity in entities)
        {
            model.Add(Mapper.Map<M>(currentEntity));
        }

        int pageNumber = (currentPage ?? 1);
        return View(model.ToPagedList(pageNumber, PageSize));
    }

    [HttpGet]
    public virtual ActionResult Add()
    {
        return View(new M());
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    [ValidateInput(false)]
    public virtual ActionResult Add(M model)
    {
        if (ModelState.IsValid)
        {
            _repository.Add(Mapper.Map<T>(model), CurrentUser);
            return RedirectToAction("Index");
        }

        return View(model);
    }

    [HttpGet]
    public virtual ActionResult Update(int modelId)
    {
        T domainModelEntity = _repository.GetById(modelId);
        M model = Mapper.Map<M>(domainModelEntity);

        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    [ValidateInput(false)]
    public virtual ActionResult Update(M model)
    {
        if (ModelState.IsValid)
        {
            _repository.Update(Mapper.Map<T>(model), CurrentUser);
            return RedirectToAction("Index");
        }

        return View(model);
    }

    public virtual ActionResult Delete(int modelId)
    {
        _repository.Delete(modelId);
        return RedirectToAction("Index");
    }
}

And this specific controller

public class WebPagesController : BaseWebEntityController<WebPage, WebPageModel>
{
    public WebPagesController(IRepository<WebPage> repository)
        : base(repository)
    {
    }
}

A little explanation. a)You should use some IOC for controller dependency resolving.b)T is for entity from database(table) and M is for Model which is returned to View.c) Automapper is well known library.
I didn't make any changes to routing. Left it default.
Hope it helps. Good luck.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi, I have a problem, i want to get table from my generic class: public virtual TEntity GetById(int id) { return _db.Set<TEntity>().FirstOrDefault(c => ((IEntity)c).Code == id); } But Linq can't cast IEntity public abstract class BaseRepository<TEntity, M> : IRepository<TEntity> where TEntity : class where M : new() If a replace "TEntity : class" with "TEntity : IEntity" i have this error : The type 'TEntity' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method 'System.Data.Entity.DbContext.Set<TEntity>() Can you help me ? Thank

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.