Unlike ramiramulu, I would refrain from introducing too many abstractions.
If you use EF, your DAL is actually Entity Framework, no need to abstract that. A lot of people attempts to do this but this only complicates your code a lot, for no gain. If you were doing SQL requests and calling stored procedures directly, then a DAL would be helpful, but building an abstraction on top of EF (which is another abstraction, or over NHibernate) is a bad idea.
Also, pure DTOs as an abstraction are more and more frown upon, but they can be used if you have a middleware and do not directly access the database - for example, a message bus like NServiceBus: messages would be considered DTOs in that case.
Unless you do very simple and pure CRUD (in which case, go ahead, put the logic in controllers - no reason to add complexity for pretty straightforward business), you should move business logic outside of your controllers for sure. For this you have many options, but 2 of the most popular are : a rich domain model with domain driven design or rich business services with service oriented design. They are a lot of ways to do this, but these 2 illustrates very different approaches.
Rich Domain (Controller per Aggregate)
In the first case, your controller would be responsible for acquiring the domain object, calling the logic, and returning a View Model. They do the bridge between the View world and the Model world. How to acquire the domain object(s) needs to be somewhat abstracted, often simple virtual methods works great - keep it simple.
Aggregate Root:
public class Item
{
public string itemId { get; set; }
public string itemDescription { get; set; }
public float unitPrice { get; set; }
// more fields
public virtual ItemProductLine itemProductLine { get; set; }
// Example of logic, should always be in your aggregate and not in ItemProductLine for example
public void UpdatePrice(float newPrice)
{
// ... Implement logic
}
}
View Model:
public class ItemViewModel
{
public int id { get; set; }
public string itemNumber { get; set; }
public String itemDescription { get; set; }
public Double unitPrice { get; set; }
public string productLine { get; set; }
}
Controller:
public class ItemController : Controller
{
[HttpGet]
public ActionResult Edit(int id)
{
var item = GetById(id);
// Some logic to map to the VM, maybe automapper, valueinjector, etc.
var model = item.MapTo<ItemViewModel>();
return View(model);
}
[HttpPost]
public ActionResult Update(int id, ItemViewModel model)
{
// Do some validation
if (!model.IsValid)
{
View("Edit", model); // return edit view
}
var item = GetById(model.id);
// Execute logic
item.UpdatePrice(model.unitPrice);
// ... maybe more logic calls
Save(item);
return RedirectToAction("Edit");
}
public virtual Item GetById(int id)
{
return dbContext.Items.Find(id);
}
public virtual bool Save(Item item)
{
// probably could/should be abstracted in a Unit of Work
dbContext.Items.Update(item);
dbContext.Save();
}
}
This works great with logic that trickles down and are very model specific. It is also great when you do not use CRUD and are very action-based (e.g. a button to update only the price compared to an edit page where you can change all item values). It is pretty decoupled and the separation of concerns is there - you can edit and test business logic on their own, you can test controllers without a backend (by overriding the virtual functions), and you do not have hundreds of abstractions built on one another. You might roll out the virtual function in a repository class, but by experience you always have very specific filters and concerns that are controller/view dependent, and often you end up with one controller per aggregate root, so controllers are a good place for them (e.g. .GetAllItemsWithAPriceGreaterThan(10.0)
)
In an architecture like that, you have to be careful about boundaries. For example, you could have a Product controller/aggregate and want to list all Items related to that product, but it should be read-only - you couldn't call any business on Items from Products - you need to navigate to the Item controller for that. The best way to do this is to automatically map to the ViewModel :
public class ProductController : Controller
{
// ...
public virtual IEnumerable<ItemViewModel> GetItemsByProductId(int id)
{
return dbContext.Items
.Where(x => ...)
.Select(x => x.MapTo<ItemViewModel>())
.ToList();
// No risks of editing Items
}
}
Rich Services (Controller per Service)
With rich services, you build a more service oriented abstraction. This is great when business logic spawns multiple boundaries and models. Services play the role of the bridge between the View and the Model. They should NEVER expose the underlying Models, only specific ViewModels (which play the role of DTO in that case). This is very good when you have a MVC site and some REST WebApi working on the same dataset for example, they can reuse the same services.
Model:
public class Item
{
public string itemId { get; set; }
public string itemDescription { get; set; }
public float unitPrice { get; set; }
// more fields
public virtual ItemProductLine itemProductLine { get; set; }
}
View Model:
public class ItemViewModel
{
public int id { get; set; }
public string itemNumber { get; set; }
public String itemDescription { get; set; }
public Double unitPrice { get; set; }
public string productLine { get; set; }
}
Service:
public class ItemService
{
public ItemViewModel Load(int id)
{
return dbContext.Items.Find(id).MapTo<ItemViewModel>();
}
public bool Update(ItemViewModel model)
{
var item = dbContext.Items.Find(model.id);
// update item with model and check rules/validate
// ...
if (valid)
{
dbContext.Items.Update(item);
dbContext.Save();
return true;
}
return false;
}
}
Controller:
public class ItemController : Controller
{
public ItemService Service { get; private set; }
public ItemController(ItemService service)
{
this.Service = service;
}
[HttpGet]
public ActionResult Edit(int id)
{
return View(Service.Load(id));
}
[HttpPost]
public ActionResult Update(int id, ItemViewModel model)
{
// Do some validation and update
if (!model.IsValid || !Service.Update(model))
{
View("Edit", model); // return edit view
}
return RedirectToAction("Edit");
}
}
Controllers are only there to call the Service(s) and compose the results for the Views. They are "dumb" compared to domain oriented controllers, but if you have a lot of views complexities (tons of composed views, ajax, complex validation, json/xml processing along side html, etc.), this is the preferred approach.
Also, in this case, services do not have to related to only one model. The same service could manipulate multiple model types if they share business logic. So an OrderService could access the inventory and make adjustments there, etc. They are more process-based than model-based.