One of the architects on the project I'm a part of, Joel Holder, has told me he prefers to have a distinction between models passed to forms and models making their way back from forms. He asserts that the FubuMVC approach that the guys at Dovetail have rolled works in such a manner. We are not using Fubu in our project, but I am trying to get my head around this Fubu concept. I've spent way too much time this evening trying to throw together an example of this Fubuesque concept. Here is what I have. There is no database. This is just a silly example. Some of the code is kinda corny. There are three objects in my core: Address, Clock and Person
using System;
namespace Core
{
public class Address
{
public Guid Id { get; set; }
public string Street { get; set; }
public Int32 ZipCode { get; set; }
}
}
...and...
using System;
namespace Core
{
public static class Clock
{
public static DateTime Now()
{
return DateTime.UtcNow;
}
}
}
...and finally we have Person. This object makes use of the pervious two and has some deeper (silly) functionality.
using System;
namespace Core
{
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime BirthDay { get; private set; }
public int Age { get; private set; }
public Address Home { get; set; }
public void setBirthDay(DateTime momentOfBirth)
{
if (momentOfBirth < Clock.Now())
{
BirthDay = momentOfBirth;
TimeSpan span = Clock.Now().Subtract(momentOfBirth);
Double roughAge = (span.Days / 365.25);
Age = Convert.ToInt32(Math.Truncate(roughAge));
}
}
public void alterBirthDay(Int32 newAge)
{
if (newAge != Age)
{
BirthDay = BirthDay.AddYears(Age - newAge);
Age = newAge;
}
}
}
}
Alright, I also have two presentation models for Person. The simple out model looks like this:
namespace MyApp.Models
{
public class PersonOutModel
{
public string Name { get; set; }
public string Age { get; set; }
public string Home { get; set; }
}
}
This suggests that I am expecting to get back from a form that has to do with adding or editing a Person three string values for Home, Age, and Name. OK, so what will I need to do to prep the form? The more complicated in model looks like this. And so, here we seem to be putting BirthDay in even though we are not getting it back and we are casting (using the word loosely) an array of Address to a Dictionary generic and making the first Address the Address associated with the Person. Why so? Well, I wish to just show the BirthDay even though I do not wish to allow users to edit the BirthDay and I am creating the Dictionary to populate a dropdown list. The first value, the default value, which will persist if the dropdown is not changed, will match the existing Address for person.Home
using System;
using System.Collections.Generic;
using Core;
namespace MyApp.Models
{
public class PersonInModel
{
public string Name { get; private set; }
public string BirthDay { get; private set; }
public string Age { get; private set; }
public Dictionary<Guid, string> Home { get; private set; }
public void PrepModel(Person person, Address[] potentialHomes)
{
Name = person.Name;
BirthDay = person.BirthDay.Month.ToString();
BirthDay = BirthDay + "/" + person.BirthDay.Day;
BirthDay = BirthDay + "/" + person.BirthDay.Year;
Age = person.Age.ToString();
Home = new Dictionary<Guid, string>();
Home.Add(person.Home.Id, person.Home.Street);
foreach (Address address in potentialHomes)
{
if (address != person.Home)
{
Home.Add(address.Id, address.Street);
}
}
}
}
}
The view looks like this:
@model MyApp.Models.PersonInModel
@using (Html.BeginForm("Index", "Home", FormMethod.Post)){
<div>
@Html.LabelFor(model => model.Name)
@Html.TextBoxFor(model => model.Name)
</div>
<div>
@Html.LabelFor(model => model.Age)
@Html.TextBoxFor(model => model.Age)
<text>Born:</text> @Html.DisplayFor(model => model.BirthDay)
</div>
<div>
@Html.LabelFor(model => model.Home)
@Html.DropDownListFor(m => m.Home, new SelectList(Model.Home, "key", "value"))
</div>
<input type="submit" />
}
Make a little more sense now? Here is my controller tying everything together. Again, because this is just a silly example I am forgoing database hydration. I'm just using Session.
using System;
using System.Web.Mvc;
using Core;
using MyApp.Models;
namespace MyApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
Address austin = new Address();
austin.Id = new Guid("75b5e18c-13c5-4211-bf8d-9f3d0117d88c");
austin.Street = "1507 Houston Street #145";
austin.ZipCode = 78756;
Address haverhill = new Address();
haverhill.Id = new Guid("bb61cd17-c176-4102-838a-9f3d0117d892");
haverhill.Street = "5055 Club Road";
haverhill.ZipCode = 33415;
Address houston = new Address();
houston.Id = new Guid("31ba86cb-991f-4e71-a2df-9e4000a8b3bc");
houston.Street = "14795 Memorial Drive";
houston.ZipCode = 77079;
Address lampasas = new Address();
lampasas.Id = new Guid("d74e7f2c-ad8d-4522-bb1e-9f3d0117d895");
lampasas.Street = "1006 East Avenue F";
lampasas.ZipCode = 76550;
Address[] addresses = new Address[] {austin, haverhill, houston, lampasas};
Session["potentialAddress"] = addresses;
Person person = new Person();
person.Id = new Guid("705b5d2b-6bed-4d6d-af29-9e4000a8b25e");
person.Name = "Tom";
person.setBirthDay(new DateTime(1974, 8, 24));
person.Home = houston;
Session["personAtHand"] = person;
PersonInModel personInModel = new PersonInModel();
personInModel.PrepModel(person, addresses);
return View(personInModel);
}
[HttpPost]
public ActionResult Index(PersonOutModel personOutModel)
{
Person person = Session["personAtHand"] as Person;
Address[] addresses = Session["potentialAddress"] as Address[];
person.Name = personOutModel.Name;
try
{
person.alterBirthDay(Convert.ToInt32(personOutModel.Age));
}
catch(InvalidCastException exception)
{
throw (exception);
}
Guid addressGuid = new Guid(personOutModel.Home);
foreach(Address address in addresses)
{
if (address.Id == addressGuid) person.Home = address;
}
Session["personAtHand"] = person;
return View("About", personOutModel);
}
}
}
I'm testing my app by just setting a breakpoint at the last real line of code before the final three curly braces and inspecting the properties on person when I reach that breakpoint.
...yields...
...and...
I think this illustrates how one might use two separate models for handing into a View something varied from what is returned. In parting, I offer the tests from my silly application as a bonus. I did have to write the tests below to iron out some bugs.
using System;
using System.Collections.Generic;
using Core;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyApp.Models;
namespace TestProject1
{
[TestClass]
public class Tests
{
[TestMethod]
public void PersonTest()
{
Person person = new Person();
person.Id = new Guid("75b5e18c-13c5-4211-bf8d-9f3d0117d88c");
person.Name = "Tom";
DateTime myTime = new DateTime(1974, 8, 24);
person.setBirthDay(myTime);
Assert.AreEqual(person.Age, 36);
Assert.AreEqual(person.Name, "Tom");
Assert.AreEqual(person.BirthDay, myTime);
Assert.AreEqual(person.Id.ToString(), "75b5e18c-13c5-4211-bf8d-9f3d0117d88c");
person.alterBirthDay(38);
myTime = new DateTime(1972, 8, 24);
Assert.AreEqual(person.BirthDay, myTime);
person.alterBirthDay(34);
myTime = new DateTime(1976, 8, 24);
Assert.AreEqual(person.BirthDay, myTime);
}
[TestMethod]
public void PersonInModelTest()
{
Address austin = new Address();
austin.Id = new Guid("75b5e18c-13c5-4211-bf8d-9f3d0117d88c");
austin.Street = "1507 Houston Street #145";
austin.ZipCode = 78756;
Address haverhill = new Address();
haverhill.Id = new Guid("bb61cd17-c176-4102-838a-9f3d0117d892");
haverhill.Street = "5055 Club Road";
haverhill.ZipCode = 33415;
Address houston = new Address();
houston.Id = new Guid("31ba86cb-991f-4e71-a2df-9e4000a8b3bc");
houston.Street = "14795 Memorial Drive";
houston.ZipCode = 77079;
Address lampasas = new Address();
lampasas.Id = new Guid("d74e7f2c-ad8d-4522-bb1e-9f3d0117d895");
lampasas.Street = "1006 East Avenue F";
lampasas.ZipCode = 76550;
Address[] addressesIn = new Address[] { austin, haverhill, houston, lampasas };
Person person = new Person();
person.Id = new Guid("705b5d2b-6bed-4d6d-af29-9e4000a8b25e");
person.Name = "Tom";
person.setBirthDay(new DateTime(1974, 8, 24));
person.Home = houston;
PersonInModel personInModel = new PersonInModel();
personInModel.PrepModel(person, addressesIn);
Assert.AreEqual(personInModel.Name,"Tom");
Assert.AreEqual(personInModel.BirthDay,"8/24/1974");
Assert.AreEqual(personInModel.Age,"36");
Assert.AreEqual(personInModel.Home.Count,4);
Int32 counter = 1;
foreach(KeyValuePair<Guid, string> values in personInModel.Home)
{
if (counter == 1)
{
Assert.AreEqual(values.Key, houston.Id);
Assert.AreEqual(values.Value, "14795 Memorial Drive");
}
if (counter == 2)
{
Assert.AreEqual(values.Key, austin.Id);
Assert.AreEqual(values.Value, "1507 Houston Street #145");
}
if (counter == 3)
{
Assert.AreEqual(values.Key, haverhill.Id);
Assert.AreEqual(values.Value, "5055 Club Road");
}
if (counter == 4)
{
Assert.AreEqual(values.Key, lampasas.Id);
Assert.AreEqual(values.Value, "1006 East Avenue F");
}
counter++;
}
}
}
}
No comments:
Post a Comment