Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Wednesday, 19 February 2014
( Humour | NLayered )

Goal:  To build out the humour website using the good parts of NLayered.

Note Part 2 refers to the NLayered pdf.

image
In c:\dev\humour

Model references Infrastructure
Repository references Infrastructure and Model
Mvc references all

Use NuGet to update all packages at solution level.  Have not enabled NuGet Package Restore as it was updating on each build.

image
Adding to source control.  Copy this line highlighted

image
Solution right click Add to Source Control, Git, do initlal commit.  Then push to remote repository.
There will be quite a lot of files going up as haven’t opted for NuGet Package Restore.

**TODO – scan list of installed packages and get rid of the ones I don’t need

| | # 
# Tuesday, 18 February 2014

Making Controllers Testable

fakepersonrepository – using a fake its much easier to test the controller now

StructureMap

using constructor based DI is prone to errors, and labour intensive… so use a DI framework

public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
// x.For<IExample>().Use<Example>();
});
returnObjectFactory.Container;

Uses a similar concept to Unity, so ISomething is requested it returns Something.

PeopleController and Views

  • use ViewModels – could be exposing too much data with Model, and could be changing Model
  • Paging required
  • Sorting required

Multiple ViewModels – eg displaying a person (fewer fields) would have different data to editing a person..

He uses AutoMapper

Car car = _carRepository.FindById(id);
CarViewModel carViewModel = newCarViewModel();
carViewModel.Id = car.Id;
carViewModel.Name = car.Name;
return View(carViewModel);

Car car = _carRepository.FindById(id);
CarViewModel carViewModel = newCarViewModel();
Mapper.Map(car, carViewModel)

AutoMapper

public static class AutoMapperConfig
{
public static voidStart()
{
Mapper.CreateMap<Person, DisplayPerson>();
// Other mappings go here
Mapper.AssertConfigurationIsValid();
}
}

Can even do it automatically if have the same name and types of members

Sorting

public ActionResult Index(string sort = "Id", string sortDir = "ASC")
{
    var sortedPeopleList = _peopleRepository
    .FindAll().OrderBy(string.Format("{0} {1}", sort, sortDir));
    // Other code here
    return View();
}

eg using a grid and want to sort based on column header

Linq doesn’t allow this.. no overload of OrderBy that accepts a string.

Can use DynamicQuery library

Paging

public ActionResult Index(int page = 1, string sort = "Id", string sortDir = "ASC")
{
    var allPeople = _peopleRepository.FindAll()
    .OrderBy(string.Format("{0} {1}", sort, sortDir)).Skip((page * PageSize) - PageSize)
    .Take(PageSize);
    var model = new List<DisplayPerson>();
    Mapper.Map(allPeople, model);
    return View(model);
}

Combining paging and sorting

public ActionResult Index(int page = 1, string sort = "Id", string sortDir = "ASC")
{
    int totalRecords = _peopleRepository.FindAll().Count();
    var data = new List<DisplayPerson>();
    IQueryable<Person> allPeople = _peopleRepository.FindAll()
    .OrderBy(BuildOrderBy(sort, sortDir)).Skip((page * PageSize) - PageSize)
    .Take(PageSize);
    Mapper.Map(allPeople, data);
    var model = new PagerModel<DisplayPerson> { Data = data, PageNumber = page, PageSize = PageSize, TotalRows = totalRecords };
    return View(model);
}

Using Count() to get proper paging links.  PagerModel<T> – made it generic which means other ViewModel types that need to support paging can use it.

public class PagerModel<T> where T : class
{
    public IEnumerable<T> Data { get; set; }
    public int PageSize { get; set; }
    public int PageNumber { get; set; }
    public int TotalRows { get; set; }
}

He is using MVC WebGrid to render:

@if (Model.Data.Any())
{
    var grid = new WebGrid(null, defaultSort: "FirstName", columnNames: new[] { "Id", "FullName", "DateOfBirth", "Type" }, rowsPerPage: Model.PageSize);
    grid.Bind(Model.Data, rowCount: Model.TotalRows, autoSortAndPage: false);

    @grid.GetHtml(columns: grid.Columns(
   grid.Column("Id"),
   grid.Column(header: "Full name", columnName: "FullName", format: (item) => Html.ActionLink(((string)item.FullName), "Details", new { item.id })),
   grid.Column("DateOfBirth", header: "Date of Birth", format: (item) => item.DateOfBirth.ToString("d")),
   grid.Column("Type", canSort: false),
   grid.Column(header: "Addresses", format: item => new HtmlString(
                Html.ActionLink("Home", "Edit", "Addresses", new { personId = item.Id, contactType = (int)ContactType.Personal }, null).ToString() + " | " +
                Html.ActionLink("Work", "Edit", "Addresses", new { personId = item.Id, contactType = (int)ContactType.Business }, null).ToString())
   ),
   grid.Column(format: (item) => Html.ActionLink("E-mail addresses", "List", "EmailAddresses", new { personId = item.id }, null)),
   grid.Column(format: (item) => Html.ActionLink("Phone numbers", "List", "PhoneNumbers", new { personId = item.id }, null)),
   grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", new { item.id })),
   grid.Column(format: (item) => Html.ActionLink("Delete", "Delete", new { item.id }))
  )
)
}

CreateAndEditPerson has dataannotations on it.

If anything fails then am passing good EF error messages back to the user.

Validation

public class CreateAndEditPerson : IValidatableObject
{
    public int Id { get; set; }

    [Required, DisplayName("First name")]
    public string FirstName { get; set; }

    [Required, DisplayName("Last name")]
    public string LastName { get; set; }

    [DisplayName("Date of birth")]
    public DateTime DateOfBirth { get; set; }

    public PersonType Type { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Type == PersonType.None)
        {
            yield return new ValidationResult("PersonType can't be None.", new[] { "Type" });
        }
    }
}

Duplication here with annotations and Validate method.. reckons is okay as potentially different validations.  eg for legacy code on model.. and tighter on ViewModel.

DropDownList

He uses a helper… better way in EF6 and MVC5

Suggests using JQueyryUI or KendoUI to make good looking UI’s.

| | # 

Security

Changed in MVC5

Concurrency

Using RowVersion.  And how to handle possible errors in the UI

SQL Data Generator

To find perf issues.  Eg sorting on Full Name wasn’t good until added the proper index.

EF Profiler from Hibernating Rhinos

Good instuctions.. however I’d like to try Glimpse and ..

NLog

Good implementation.. potentiallly use other logging implementation.. this.log?

SQL Compare

I use VS Schema and data compare tools

| | # 
# Sunday, 16 February 2014

Install via NuGet into the Repositories project, and IntegrationTests project

Only the Person is the AggregateRoot.. so only need a DbSet<T> properties and later on repository for that.

Then he is creating a Factory pattern for repositories

Configuring your Model’s Business Rules

Property level validation using attributes

Using fluent API

IValidateableObject – Object Level Validation

 

Database Initialization and Migrations

sdf

Base Repository Class

asd

Search

asdf

Unit Of Work

asfd

IDateTracking

asdf

Improving Error Messages

asdf

| | # 

Entities

The canonical example is the Personclass. If two Personinstances have the same name, do you consider them to represent the same person?
Most likely not, as the name would not uniquely identify the person, and there’s a high probability that even though
these instances contain the same name they refer to two different people in the real world

Value Objects

A Value Object on the other hand is identified by its properties and the values they contain. The canonical example
here is Address: two instances of Addressthat contain the data “327 Washington Blvd Venice, California 90291” are
most likely considered the same; they don’t have (or need) an identity on their own.

image
And People class inherits off CollectionBase.  Notice Address is missing as app doesn’t need it.

 

namespace DLayer.Infrastructure
{
    public abstract class DomainEntity<T>
    {
        /// <summary>
        /// Gets or sets the unique ID of the entity in the underlying data store.
        /// </summary>
        public T Id { get; set; }
        /// <summary>
        /// Checks if the current domain entity has an identity.
        /// </summary>
        /// <returns>True if the domain entity is transient (i.e. has no identity yet),
        /// false otherwise.
        /// </returns>
        public bool IsTransient()
        {
            return Id.Equals(default(T));
        }
    }
}
public class Person : DomainEntity<int>
{
}

default returns the type’s default value. 

So using Id, whereas usually I prefer to use PersonId

He does low level annotations at the Entity level eg Required:

[TestMethod]
public void FirstAndLastNameResultsInFullName()
{
    var person = new Person() { FirstName = "Imar", LastName = "Spaanjaars" };
    person.FullName.Should().Be("Imar Spaanjaars");
}

[TestMethod]
public void EmptyFirstNameReturnsLastName()
{
    var person = new Person() { LastName = "Spaanjaars" };
    person.FullName.Should().Be("Spaanjaars");
}

[TestMethod]
public void EmptyLastNameReturnsFirstName()
{
    var person = new Person() { FirstName = "Imar" };
    person.FullName.Should().Be("Imar");
}

[TestMethod]
public void AllEmptyReturnsEmpty()
{
    var person = new Person();
    person.FullName.Should().Be(string.Empty);
}

and implementation:

public class Person : DomainEntity<int>
{
    [Required]
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public PersonType Type { get; set; }

    public string FullName
    {
        get
        {
            string temp = FirstName ?? string.Empty;
            if (!string.IsNullOrEmpty(LastName))
            {
                if (temp.Length > 0)
                {
                    temp += " ";
                }
                temp += LastName;
            }
            return temp;
        }
    }
}

Notice how the tests will pass as we’re not worried about IValidateable yet.

Also as this is a readonly property EF will ignore it.

namespace DLayer.Model
{
  /// <summary>
  /// Represents an e-mail address in the system.
  /// </summary>
    public class EmailAddress : DomainEntity<int>
    {
        /// <summary>
        /// Gets or sets the text of the e-mail address.
        /// </summary>
        [Required]
        [EmailAddressAttribute]
        public string EmailAddressText { get; set; }

        /// <summary>
        /// Gets or sets the type of the e-mail address.
        /// </summary>
        public ContactType ContactType { get; set; }

        /// <summary>
        /// Gets or sets the owner (a Person) of the e-mail address.
        /// </summary>
        public Person Owner { get; set; }

        /// <summary>
        /// Gets or sets the ID of the owner (a Person) of the e-mail address.
        /// </summary>
        public int OwnerId { get; set; }
    }
}

Hmm – XML comments cluttering up I’d say.  Also ambiguous Owner and OwnerId… why not Person and PersonId.?

Foreign Key Properties

This is Owner and OwnerId above
So we can shortcut..ie don’t need this:

var emailAddress = new EmailAddress { ... };
emailAddress.Owner = _personRepository.FindById(123)

can have this:

var emailAddress = new EmailAddress { ... };
emailAddress.OwnerId = 123

which could save a trip to the db eg when creating a new email address and all we have is the personId passed in the query string.

ValueObject

public class Address : ValueObject<Address>
{
    #region Constructors

    /// <summary>
    /// Initializes a new instance of the Address class.
    /// The constructor is marked private because we want other consuming code to use the overloaded constructor.
    /// However, EF still needs a parameterless constructor.
    /// </summary>
    private Address() { }

    /// <summary>
    /// Initializes a new instance of the Address class.
    /// </summary>
    /// <param name="street">The street of this address.</param>
    /// <param name="city">The city of this address.</param>
    /// <param name="zipCode">The zip code of this address.</param>
    /// <param name="country">The country of this address.</param>
    /// <param name="contactType">The type of this address.</param>
    public Address(string street, string city, string zipCode, string country, ContactType contactType)
    {
        Street = street;
        City = city;
        ZipCode = zipCode;
        Country = country;
        ContactType = contactType;
    }

    /// <summary>
    /// Gets the street of this address.
    /// </summary>
    public string Street { get; private set; }

    /// <summary>
    /// Gets the zip code  of this address.
    /// </summary>
    public string ZipCode { get; private set; }

    /// <summary>
    /// Gets the city of this address.
    /// </summary>
    public string City { get; private set; }

    /// <summary>
    /// Gets the country of this address.
    /// </summary>
    public string Country { get; private set; }

    /// <summary>
    /// Gets the contact type of this address.
    /// </summary>
    public ContactType ContactType { get; private set; }

    /// <summary>
    /// Determines if this address can be considered to represent a "null" value.
    /// </summary>
    // <returns>True when all four properties of the address contain null; false otherwise.
    public bool IsNull
    {
        get
        {
            return (string.IsNullOrEmpty(Street) && string.IsNullOrEmpty(ZipCode) && string.IsNullOrEmpty(City) && string.IsNullOrEmpty(Country));
        }
    }
}

Make it immutable

EF not Deleting Collections

var person = myContext.People.First(x => x.Id = id);
person.EmailAddresses.Clear();
myContext.SaveChanges()

This doesn’t delete the associated EmailAddresses.  EF just clears the FK that points to the Person (from each EmailAddress).  Trick is to use IHasOwner our own custom interface to detect.

So EmailAddress and PhoneNumber implement IHasOwner (which is why we use Owner in those classes)

Implementing Equality Comparison

eg we’re going to need to see if 2 addresses are the same eg business and personal of a person.

eg see if a person is being entered twice

quite complex here…

Creating Collections

he’s got a CollectionBase

going towards:

Person person = new Person();
person.EmailAddresses.Add("imar@spaanjaars.com", ContactType.Business)

Automatic Tracking of Creation and Modification Dates

Instead of:

myPerson.DateModified = DateTime.Now

hmm can get EF to automatically manage these properties

public class Person : DomainEntity<int>, IDateTracking
namespace DLayer.Model
{
    /// <summary>
    /// Defines an interface for objects whose creation and modified dates are kept track of.
    /// </summary>
    public interface IDateTracking
    {
        /// <summary>
        /// Gets or sets the date and time the object was created.
        /// </summary>
        DateTime DateCreated { get; set; }

        /// <summary>
        /// Gets or sets the date and time the object was last modified.
        /// </summary>
        DateTime DateModified { get; set; }
    }
}

Putting It All Together

eg a Person should be given collection classes for EmailAddress and PhoneNumber classes.  All these properties should be instantiated in the constructor so can access them immediately.

public class Person : DomainEntity<int>, IDateTracking
  {
    #region Constructors

    /// <summary>
    /// Initializes a new instance of the Person class.
    /// </summary>
    public Person()
    {
      EmailAddresses = new EmailAddresses();
      PhoneNumbers = new PhoneNumbers();
      HomeAddress = new Address(null, null, null, null, ContactType.Personal);
      WorkAddress = new Address(null, null, null, null, ContactType.Business);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the date and time the object was created.
    /// </summary>
    public DateTime DateCreated { get; set; }

    /// <summary>
    /// Gets or sets the date and time the object was last modified.
    /// </summary>
    public DateTime DateModified { get; set; }

    /// <summary>
    /// Gets or sets the first name of this person.
    /// </summary>
    [Required]
    public string FirstName { get; set; }

    /// <summary>
    /// Gets or sets the last name of this person.
    /// </summary>
    [Required]
    public string LastName { get; set; }

    /// <summary>
    /// Gets or sets the date of birth of this person.
    /// </summary>
    public DateTime DateOfBirth { get; set; }

    /// <summary>
    /// Gets or sets the type of person.
    /// </summary>
    public PersonType Type { get; set; }

    /// <summary>
    /// Gets or sets the home address of this person.
    /// </summary>
    public Address HomeAddress { get; set; }

    /// <summary>
    /// Gets or sets the work address of this person.
    /// </summary>
    public Address WorkAddress { get; set; }

    /// <summary>
    /// Gets or sets the e-mail addresses of this person.
    /// </summary>
    public EmailAddresses EmailAddresses { get; private set; }

    /// <summary>
    /// Gets or sets the phone numbers of this person.
    /// </summary>
    public PhoneNumbers PhoneNumbers { get; set; }

    /// <summary>
    /// Gets the full name of this person.
    /// </summary>
    public string FullName
    {
      get
      {
        string temp = FirstName ?? string.Empty;
        if (!string.IsNullOrEmpty(LastName))
        {
          if (temp.Length > 0)
          {
            temp += " ";
          }
          temp += LastName;
        }
        return temp;
      }
    }

XML comments hide in VS with http://visualstudiogallery.msdn.microsoft.com/03141ea7-c35e-4533-b05b-9e60545e93eb

Defining Repository Interface

namespace Spaanjaars.Infrastructure
{
  /// <summary>
  /// Defines various methods for working with data in the system.
  /// </summary>
  public interface IRepository<T, K> where T : class
  {
    /// <summary>
    /// Finds an item by its unique ID.
    /// </summary>
    /// <param name="id">The unique ID of the item in the database.</param>
    /// <param name="includeProperties">An expression of additional properties to eager load. For example: x => x.SomeCollection, x => x.SomeOtherCollection.</param>
    /// <returns>The requested item when found, or null otherwise.</returns>
    T FindById(K id, params Expression<Func<T, object>>[] includeProperties);

    /// <summary>
    /// Returns an IQueryable of all items of type T.
    /// </summary>
    /// <param name="includeProperties">An expression of additional properties to eager load. For example: x => x.SomeCollection, x => x.SomeOtherCollection.</param>
    /// <returns>An IQueryable of the requested type T.</returns>
    IQueryable<T> FindAll(params Expression<Func<T, object>>[] includeProperties);

    /// <summary>
    /// Returns an IQueryable of items of type T.
    /// </summary>
    /// <param name="predicate">A predicate to limit the items being returned.</param>
    /// <param name="includeProperties">An expression of additional properties to eager load. For example: x => x.SomeCollection, x => x.SomeOtherCollection.</param>
    /// <returns>An IEnumerable of the requested type T.</returns>
    IEnumerable<T> FindAll(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties);

    /// <summary>
    /// Adds an entity to the underlying collection.
    /// </summary>
    /// <param name="entity">The entity that should be added.</param>
    void Add(T entity);

    /// <summary>
    /// Removes an entity from the underlying collection.
    /// </summary>
    /// <param name="entity">The entity that should be removed.</param>
    void Remove(T entity);

    /// <summary>
    /// Removes an entity from the underlying collection.
    /// </summary>
    /// <param name="id">The ID of the entity that should be removed.</param>
    void Remove(K id);
  }
}

Summary of this section

- very interesting way the model is being built up and tested.

| | #