Developing ASP.NET MVC 3 Application with EF CTP (Part 3)

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.

Leave a Reply

Your email address will not be published. Required fields are marked *