Today I will concentrate on refactoring existing code and adding other links to my application, such as delete and edit links.
First things first, let’s do a bit of refactoring in advance of adding more views. As I noticed, edit, delete and create views are the same with the exception of title for the submit button, So, I am going to create a partial view that encompasses basic edit functionality along with submit button. I am going to extract the title into a variable using TempData dictionary off the ViewResult object. For example, here is how I am going to put “Delete” label into the view result:
view.TempData.Add("Action", "Delete");
I will use the same Action key in create and add methods as well. Here is what code in my controller look like for the entire delete method:
public ActionResult Delete(int id)
{
BlogEntry entry = null;
using (BlogContext context = new BlogContext())
{
entry = context.Entries.Find(id);
entry.CategoryID = entry.Category.CategoryId;
entry.Categories = GetCaregories();
}
var view = View(entry);
view.TempData.Add("Action", "Delete");
return view;
}
Now I am going to refactor the view itself. I am creating new view following the same routing as in part 2 of the post, but I will check Partial View checkbox. Here is what my partial view (CreatePartial.cshtml) looks like:
@model MvcSampleApp.Models.BlogEntry
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Blog Post</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.DropDownListFor(model=>model.CategoryID,
new SelectList(Model.Categories, "CategoryId", "CategoryName", Model.CategoryID),
"– Select Category –")
@Html.ValidationMessageFor(model => model.CategoryID)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.PostText)
</div>
<div class="editor-field">
@Html.TextAreaFor(model => model.PostText)
@Html.ValidationMessageFor(model => model.PostText)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.PostedOn)
</div>
<div class="editor-field">
@Html.TextBox("PostedOn", string.Format("{0:MM/dd/yyyy}", Model.PostedOn))
@Html.ValidationMessageFor(model => model.PostedOn)
</div>
<div>
<input type="submit" value=@TempData["Action"] />
</div>
@Html.Hidden("BlogEntryId")
</fieldset>
}
Now, our old Create view looks as follows:
@model MvcSampleApp.Models.BlogEntry
@{
View.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
@{Html.RenderPartial("CreatePartial", Model);}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Super simple. Delete and Edit views look identical with exception of the title. Very clean end result for all my refactoring efforts. Here is the final code for my controller:
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web.Mvc;
using MvcSampleApp.Models;
namespace MvcSampleApp.Controllers
{
public class BlogsController : Controller
{
//
// GET: /Blogs/
public ActionResult Index()
{
using (BlogContext context = new BlogContext())
{
context.Database.Connection.ConnectionString =
ConfigurationManager.ConnectionStrings["BlogsConnectionString"].ConnectionString;
var blogs = (from one in context.Entries
orderby one.PostedOn descending
select one).ToList();
return View(blogs);
}
}
//
// GET: /Blogs/Create
public ActionResult Create()
{
IEnumerable<BlogCategory> categories = GetCaregories();
var view = View(new BlogEntry() { Categories = categories });
view.TempData.Add("Action", "Create");
return view;
}
private static IEnumerable<BlogCategory> GetCaregories()
{
IEnumerable<BlogCategory> categories;
using (BlogContext context = new BlogContext())
{
categories = (from one in context.Categories
orderby one.CategoryName
select one).ToList();
}
return categories;
}
private BlogCategory GetCategory(int categoryID)
{
using (BlogContext context = new BlogContext())
{
return context.Categories.Find(categoryID);
}
}
//
// POST: /Blogs/Create
[HttpPost]
public ActionResult Create(BlogEntry entry)
{
try
{
if (entry.CategoryID > 0)
{
entry.Category = GetCategory(entry.CategoryID);
}
if (TryValidateModel(entry))
{
using (BlogContext context = new BlogContext())
{
context.Entries.Add(entry);
context.SetAsUnchanged(entry.Category);
context.SaveChanges();
}
return RedirectToAction("Index");
}
else
{
entry.Categories = GetCaregories();
return View(entry);
}
}
catch
{
return View();
}
}
//
// GET: /Blogs/Edit/5
public ActionResult Edit(int id)
{
BlogEntry entry = null;
using (BlogContext context = new BlogContext())
{
entry = context.Entries.Find(id);
entry.CategoryID = entry.Category.CategoryId;
entry.Categories = GetCaregories();
}
var view = View(entry);
view.TempData.Add("Action", "Update");
return view;
}
//
// POST: /Blogs/Edit/5
[HttpPost]
public ActionResult Edit(int id, BlogEntry entry)
{
try
{
if (entry.CategoryID > 0)
{
entry.Category = GetCategory(entry.CategoryID);
}
if (TryValidateModel(entry))
{
entry.BlogEntryId = id;
using (BlogContext context = new BlogContext())
{
context.Entries.Attach(entry);
context.SetAsModified(entry);
context.SaveChanges();
}
return RedirectToAction("Index");
}
else
{
entry.Categories = GetCaregories();
return View(entry);
}
}
catch
{
return View();
}
}
//
// GET: /Blogs/Delete/5
public ActionResult Delete(int id)
{
BlogEntry entry = null;
using (BlogContext context = new BlogContext())
{
entry = context.Entries.Find(id);
entry.CategoryID = entry.Category.CategoryId;
entry.Categories = GetCaregories();
}
var view = View(entry);
view.TempData.Add("Action", "Delete");
return view;
}
//
// POST: /Blogs/Delete/5
[HttpPost]
public ActionResult Delete(int id, BlogEntry entry)
{
try
{
if (entry.CategoryID > 0)
{
entry.Category = GetCategory(entry.CategoryID);
}
if (TryValidateModel(entry))
{
entry.BlogEntryId = id;
using (BlogContext context = new BlogContext())
{
context.Entries.Attach(entry);
context.Entries.Remove(entry);
context.SaveChanges();
}
return RedirectToAction("Index");
}
else
{
entry.Categories = GetCaregories();
return View(entry);
}
}
catch
{
return View();
}
}
}
}
A couple more things to notice. By default foreign keys are created with Cascade Deletes option. This is not appropriate behavior for my use case. I am changing this behavior in OnModelCreating method of my context. Here is the final version of my context class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Configuration;
using System.Data.Entity.ModelConfiguration;
namespace MvcSampleApp.Models
{
public class BlogContext : DbContext
{
public BlogContext()
{
this.Database.Connection.ConnectionString =
ConfigurationManager.ConnectionStrings["BlogsConnectionString"]
.ConnectionString;
}
public DbSet<BlogCategory> Categories { get; set; }
public DbSet<BlogEntry> Entries { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BlogEntry>().HasKey(a => a.BlogEntryId);
modelBuilder.Entity<BlogCategory>().HasKey(a => a.CategoryId);
modelBuilder.Entity<BlogEntry>().HasRequired(e => e.Category).WithMany().WillCascadeOnDelete(false);
}
public void SetAsModified(object entry)
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(entry, System.Data.EntityState.Modified);
}
public void SetAsUnchanged(object entry)
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(entry, System.Data.EntityState.Unchanged);
}
}
}
I am using helper methods to SetAs… to ensure that I am not creating duplicate categories. You can see how I am using these helper methods above in controller class.
You can download entire solution here.