Fat Controllers: How Can I Make It Thin?
I am developing a blog engine using EF 6 and MVC 5.
I chose not to use the repository pattern or UoW as it is already implemented in EF 6 at the infrastructure level.
The solution contains the following layers.
DataModels Level: It has simple POCOs that are auto-generated and dbContext.
public partial class Article
{
public int Id { get; set; }
public string Slug { get; set; }
public string Title { get; set; }
public string PostBody { get; set; }
public System.DateTime CreatedOn { get; set; }
public bool IsPublished { get; set; }
public string Author { get; set; }
}
Service level:
public interface IBlogEngine
{
List<Article> GetFrontPageBlogPosts();
void SaveArticle(Article article);
List<Article> GetArticlesByStatus(string isPublished);
Article GetBySlug(string slug);
Article GetById(int id);
bool Exists(string slugUrl);
void Delete(int id);
}
IBlogEngine
... Some methods have been omitted for brevity.
public class BlogEngine : IBlogEngine
{
private readonly dbContext _context;
public BlogEngine(DbContext context)
{
_context = context;
}
public void SaveArticle(Article article)
{
if (article.Id == 0)
{
_context.Articles.Add(article);
}
else
{
_context.Entry(article).State = EntityState.Modified;
}
_context.SaveChanges();
}
public Article GetBySlug(string slug)
{
return _context.Articles.SingleOrDefault(x => x.Slug == slug.Trim());
}
}
User interface level
public class ArticleController : Controller
{
private readonly IBlogEngine _engine;
public ArticleController(IBlogEngine engine)
{
_engine = engine;
}
[HttpGet]
public ActionResult Edit(string slug)
{
if (string.IsNullOrWhiteSpace(slug))
{
return HttpNotFound();
}
var article = _engine.GetBySlug(slug);
if (article == null)
{
return HttpNotFound();
}
var model = new EditViewModel { Id = article.Id, Slug = article.Slug,
Title = article.Title, PostBody = article.PostBody, IsPublished = true };
return View("Create", model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel blogPost)
{
if (!ModelState.IsValid)
{
return View("Create", blogPost);
}
// Get Article by Id
var article = _engine.GetById(blogPost.Id);
if (article == null)
{
return HttpNotFound();
}
// Update it
article.Id = blogPost.Id;
article.Title = blogPost.Title.Trim();
article.Slug = blogPost.Slug.ToUrlSlug();
article.PostBody = blogPost.PostBody;
article.CreatedOn = DateTime.UtcNow;
article.IsPublished = blogPost.IsPublished;
article.Author = User.Identity.Name;
// Save it
_engine.SaveArticle(article);
return RedirectToAction("Create", "Article");
}
}
Problem Consider a scenario where a user has finished editing their old blog post / article and clicked the submit button to update the blog post / article.
Is my HTTP POST Change Action Too Fat? I feel like the controller is doing something here too.
-
Get an existing article from the database
-
Update it with ViewModel values
-
Call the
SaveArticle
method from the service layer.
How can I put this controller on a diet?
Shouldn't the Service Layer method SaveArticle
perform the task of fetching the article from the Db and updating it with new values and calling the SaveChanges method?
If the above statement is true, how do you pass the ViewModel method to the ServiceLayer method? Isn't it a bad solution to allow passing ViewModels into the service layer?
How can I handle this? I am confused and need some help.
source to share
To be honest, this also confuses me, and sometimes what and how the controller should do to submit the request.
In most of my implementations, I do the following:
- Accept input object viewModel in Post method (input values are validated on client side).
- Check ModelState.
- Convert the viewmodel object to a domain model object. I am using AutoMapper .
- Give it to the service method. He does what needs to be done for the operation.
- Returns the appropriate result based on the operation.
I was you, I would write:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel blogPost)
{
if (!ModelState.IsValid)
{
return View("Create", blogPost);
}
// Use AutoMapper for ViewModel to DomainModel conversion
var blogPostDomainModel = Mapper.Map<EditViewModel, BlogPost>(blogPost);
// Save it - Update the object in persistent store. It may throw
// exception if something wrong while updating the object. Having
// validated input from UI that should only happen due to server
// error.
_engine.SaveArticle(blogPostDomainModel);
return RedirectToAction("List", "Article");
}
source to share