While pair programming with some great developers I asked the question: How would you refactor this project (the running project website).
When both of the guys I paired with suggested that it would be smart to move to MVC and to use an ORM, I listened.
After downloading ASP.NET MVC version 1. I’ve been working through the tutorial from Rob Conery:
http://www.asp.net/learn/mvc-videos/video-8143.aspx (about 80mins)
Creating a New MVC Project
Ctrl Shift N
Linq to SQL
going to use Membership for login / users.
In models, add new item: LINQ to SQL
drag on the two tables
Original table name was dinners. The class is called dinner
Renamed NerddinnerDataContect to DB, and context namespace to Nerddinner
however Entity Namespace to Nerddinner.Model – which didn’t work! I left it blank.
Adding a Controller
The default Index controller
var db = new DB();
var dinners = db.Dinners;
return View(dinners);
Adding a View
right click inside method in controller
Create strongly-typed view
View content – code gen nice.
Tooling figured out which is the primary key (as linq to sql)
ModelBinders - Add
When creating a new Dinner, we use ModelBinders.. mvc looks at the Object and sees if it can bind to the linqtosql.
Could have used the existing FormCollection, but this is easier!
If ModelState.IsValid .. nice.
And by changing the Html.ValidationMessage
Editing Data
In the controller:
public ActionResult Edit(int id)
{
var db = new DB();
var dinner = db.Dinners.SingleOrDefault(x => x.DinnerID == id);
return View(dinner);
}
This is another way of expressing the above:
public ActionResult Edit(int id)
{
var db = new DB();
//var dinner = db.Dinners.SingleOrDefault(x => x.DinnerID == id);
var dinner = (from a in db.Dinners
where a.DinnerID == id
select a).SingleOrDefault();
return View(dinner);
}
<% gator tags
Other view engines – Spark or nHaml
So we’ve built scaffolding.
Routing
We want /dinners to the be root of the site.
Routing is the thing that listens for URL’s and passes them to a controller.
eg Dinner / Edit /5
eg { controller} / {action} / {id}
To change to be /dinners as the route in global.asax
static public void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Dinner", action = "Index", id = ""} // Parameter defaults
);
}
We changed from Home to Dinner above.
Deleting
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id)
{
var db = new DB();
var dinner = (from a in db.Dinners
where a.DinnerID == id
select a).SingleOrDefault();
db.Dinners.DeleteOnSubmit(dinner);
db.SubmitChanges();
return RedirectToAction("Index");
}
are wired up on the view:
<% using (Html.BeginForm("delete", "dinner")) {%>
<%=Html.Hidden("id",Model.DinnerID) %>
<input id="Submit1" type="submit" value="delete" />
<% } %>
We are not using any authentication to stop people doing this at the moment. For that we need:
[AcceptVerbs(HttpVerbs.Post)]
[Authorize(Roles="Administrator")]
public ActionResult Delete(int id)
{
var db = new DB();
Testing / Repository Pattern
Because the controller has database connection inside of it, this isn’t good from a testing point of view, so we’re going to refactor this out.
Speed would be a problem with lots of tests, as would not knowing what is in the database.
Created an IDinnerRepository interface:
public interface IDinnerRepository
{
IQueryable<Dinner> FindAllDinners();
Dinner GetDinner(int id);
void Add(Dinner dinner);
void Update(Dinner dinner);
void Delete(Dinner dinner);
void Save();
}
Then a SqlDinnerRepository that implements this, where the constructors calls the DB:
public class SqlDinnerRepository : IDinnerRepository
{
DB db;
public SqlDinnerRepository()
{
db = new DB();
}
public IQueryable<Dinner> FindAllDinners()
{
return db.Dinners;
}
public Dinner GetDinner(int id)
{
var dinner = (from a in db.Dinners
where a.DinnerID == id
select a).SingleOrDefault();
return dinner;
}
Then in the Dinner Controller:
public class DinnerController : Controller
{
IDinnerRepository _repository;
public DinnerController()
{
_repository = new SqlDinnerRepository();
}
// this will be the one that the test uses
public DinnerController(IDinnerRepository repository)
{
_repository = repository;
}
//
// GET: /Dinner/
public ActionResult Index()
{
//var db = new DB();
//var dinners = db.Dinners;
var dinners = _repository.FindAllDinners();
return View(dinners);
}
The plan is that we will make up something that implements IDinnerRepository when testing to make our life easier.
Made: FadinnerkeDinnerRepository
Made: DinnerControllerTests
Ctrl R T – Run tests
Found error in test code:
//var data = result.ViewData.Model as IList<Dinner>; // this didn't work.. hmmmmmmm.
var data = result.ViewData.Model as IEnumerable<Dinner>;
TDD
Made a test that dinners should only return dates that are today or later:
[TestMethod]
public void Index_Should_Return_Dinners_For_Today_Or_Later() {
// Arrange
var controller = new DinnerController(new Fakes.FakeDinnerRepository());
// Act
//ViewResult result = (ViewResult) controller.Index();
var result = controller.Index() as ViewResult;
// Assert
var data = result.ViewData.Model as IEnumerable<Dinner>;
Assert.IsFalse(data.Where(x=>x.EventDate<DateTime.Now).Count()>0);
}
Failed the test, then wrote the code which was in the DinnerController:
var dinners = _repository.FindAllDinners().Where(x=>x.EventDate >= DateTime.Now);
Lambda expressions
var dinners = _repository.FindAllDinners().Where(x=>x.EventDate >= DateTime.Now);
Presentation Model Pattern or View Model
45min into Rob’s video.