I can't use dependency injection to solve this because I am requesting extra parameters that are dependent on user information in the constructor. My constructor takes in IDataLayer, MessagePasser and AuthUserHelper, which is common for all the models, but I'm also asking for two int IDs, which rely on some hidden input fields or querystring parameters.
Therefore, I am going to split the properties of the model into it's own class (the model) and the actual processing such as loading and saving into a service class. The model is just a property bag and can have a parameterless constructor, while the service class would be called to populate and save data. I hope this answer helps someone out there.
So, before I had:
public class EditFamilyMemberModel
{
#region Constructor
public EditFamilyMemberModel(ITracktionDataLayer dataLayer, MessagePasser messager, AuthUserHelper authUser, int originalPersonID, int familyMemberID)
{
_dataLayer = dataLayer;
_originalPerson = new PersonAddEditModel(messager, dataLayer, authUser, originalPersonID);
_model = new PersonAddEditModel(messager, dataLayer, authUser, familyMemberID);
_grades = _dataLayer.GetGradesForOrganization(authUser.OrganizationID, _model.Person.Grade, true).ToArray();
}
#endregion
#region Private
ITracktionDataLayer _dataLayer;
PersonAddEditModel _originalPerson;
PersonAddEditModel _model;
IList<Grade> _grades;
#endregion
#region Properties
#region Read Only
public Person Person
{
get { return _model.Person; }
}
public AuthUserHelper AuthUser
{
get { return _model.AuthUser; }
}
public IList<School> Schools
{
get { return _model.Schools; }
}
public IList<ContactType> ContactTypes
{
get { return _model.ContactTypes; }
}
public IList<string> Genders
{
get { return _model.Genders; }
}
public IList<Grade> Grades
{
get { return /*_model.Grades;*/ _grades; }
}
public IList<USState> USStates
{
get { return _model.USStates; }
}
public IList<PersonType> PersonTypes
{
get { return _model.PersonTypes; }
}
public IList<FamilyRole> FamilyRoles
{
get { return _model.FamilyRoles; }
}
#endregion
[Required]
[StringLength(100)]
public string Address
{
get { return _model.Person.Address; }
set { _model.Person.Address = value; }
}
[StringLength(100)]
public string Address2
{
get { return _model.Person.Address2; }
set { _model.Person.Address2 = value; }
}
[Required]
[StringLength(50)]
public string City
{
get { return _model.Person.City; }
set { _model.Person.City = value; }
}
[Required]
[StringLength(50)]
public string State
{
get { return _model.Person.State; }
set { _model.Person.State = value; }
}
[Required]
[StringLength(50)]
public string Zip
{
get { return _model.Person.Zip; }
set { _model.Person.Zip = value; }
}
[DataType(DataType.Date)]
//[Required]
public DateTime? DOB
{
get { return _model.Person.DOB; }
set { _model.Person.DOB = value; }
}
[Required]
public string Gender
{
get { return _model.Person.Gender; }
set { _model.Person.Gender = value; }
}
[Required]
public string FirstName
{
get { return _model.Person.FirstName; }
set { _model.Person.FirstName = value; }
}
[Required]
public string LastName
{
get { return _model.Person.LastName; }
set { _model.Person.LastName = value; }
}
public int? SchoolID
{
get { return _model.Person.SchoolID; }
set { _model.Person.SchoolID = value; }
}
public int? GradeID
{
get { return _model.Person.Grade; }
set { _model.Person.Grade = value; }
}
public string SpecialNeedsDescription
{
get { return _model.Person.SpecialNeedsDescription; }
set { _model.Person.SpecialNeedsDescription = value; }
}
public string SpecialNeedsSummary
{
get { return _model.Person.SpecialNeedsSummary; }
set { _model.Person.SpecialNeedsSummary = value; }
}
public byte[] PictureData
{
get;
set;
}
public string PictureAction
{
get;
set;
}
#region Membership
[Display(Name = "User Name")]
[DisplayName("User Name")]
[UniqueUserName(ErrorMessage = "The user name specified is already in use, please choose another user name.")]
public string Username { get; set; }
[Display(Name = "Password")]
[DisplayName("Enter a password")]
[StringLength(50, MinimumLength = 6, ErrorMessage = "Password must be at least 6 characters long.")]
public string Password { get; set; }
[DisplayName("Confirm Password")]
[StringLength(50, MinimumLength = 6)]
public string PasswordConfirm { get; set; }
#endregion
#region Abstracted Contact Methods
/// <summary>
/// When adding someone, this represents the phone number contact record.
/// </summary>
[Display(Name = "Home Phone Number")]
[DisplayName("Home Phone Number")]
[USPhoneNumber]
//[Required]
public string HomePhoneNumber
{
get { return _model.HomePhone; }
set { _model.HomePhone = value; }
}
/// <summary>
/// When adding someone, this represents the phone number contact record.
/// </summary>
[Display(Name = "Cell Phone Number")]
[DisplayName("Cell Phone Number")]
[USPhoneNumber]
//[Required]
public string CellPhoneNumber
{
get { return _model.PersonalCell; }
set { _model.PersonalCell = value; }
}
/// <summary>
/// When adding someone, this represents the email address contact record.
/// </summary>
[Display(Name = "Email Address")]
[DisplayName("Email Address")]
//[Required] -- Some parents dont have email addresses
[UniqueEmailAddress(ErrorMessage = "The email address was already found in our system, please sign in or use another email.")]
[Email(ErrorMessage = "The email address specified isn't an email address.")]
public string EmailAddress
{
get { return _model.EmailAddress; }
set { _model.EmailAddress = value; }
}
#endregion
#endregion
#region Logic
public void SaveChanges()
{
_model.Person.ChangedTimeStamp = DateTime.Now;
_model.Person.ChangedBy = -1;
// If we have a valid home #, add it
if (!string.IsNullOrWhiteSpace(HomePhoneNumber))
_model.HomePhone = HomePhoneNumber;
// If we have a valid cell #, add it
if (!string.IsNullOrWhiteSpace(CellPhoneNumber))
_model.PersonalCell = CellPhoneNumber;
// If we have a valid email, add it
if (!string.IsNullOrWhiteSpace(EmailAddress))
_model.EmailAddress = EmailAddress;
// Some fields may have 'not applicable'; blank them
_model.Person.SpecialNeedsDescription = _model.Person.SpecialNeedsDescription.NormalizeNotApplicable().NullIfEmptyOrWhitespace();
_model.Person.SpecialNeedsSummary = _model.Person.SpecialNeedsSummary.NormalizeNotApplicable().NullIfEmptyOrWhitespace();
// Names of people should be proper case if they're all entered in lowercase
_model.Person.FirstName = _model.Person.FirstName.NormalizeName();
_model.Person.LastName = _model.Person.LastName.NormalizeName();
if (PictureData != null && (PictureAction == "replace" || (PictureAction ?? "") == ""))
{
// Replace just adds, it does not remove the old picture.
Photo pic = PhotoHandling.GetPhotoEntityFromBytes(PictureData);
pic.ROWGUID = Guid.NewGuid();
pic.OrganizationID = _model.AuthUser.OrganizationID;
pic.CreationTimestamp = DateTime.Now;
pic.Deleted = false;
_model.AddNewPhoto(pic);
_model.Person.Avatar = pic.ROWGUID;
}
else if (PictureAction == "remove" && _model.Person.Avatar.HasValue)
{
var pic = _model.Person.Photos.SingleOrDefault(p => p.ROWGUID == _model.Person.Avatar.Value);
if (pic != null)
{
pic.Deleted = true;
_model.Person.Avatar = null;
}
}
_model.SaveChanges();
}
#endregion
}
Now I have split it into:
public class EditFamilyMemberModel
{
public EditFamilyMemberModel() { }
#region Properties
[Required]
[StringLength(100)]
public string Address { get; set; }
[StringLength(100)]
public string Address2 { get; set; }
[Required]
[StringLength(50)]
public string City { get; set; }
[Required]
[StringLength(50)]
public string State { get; set; }
[Required]
[StringLength(50)]
public string Zip { get; set; }
[DataType(DataType.Date)]
//[Required]
public DateTime? DOB { get; set; }
[Required]
public string Gender { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? SchoolID { get; set; }
public int? GradeID { get; set; }
public string SpecialNeedsDescription { get; set; }
public string SpecialNeedsSummary { get; set; }
public byte[] PictureData { get; set; }
public string PictureAction { get; set; }
#region Membership
[Display(Name = "User Name")]
[DisplayName("User Name")]
[UniqueUserName(ErrorMessage = "The user name specified is already in use, please choose another user name.")]
public string Username { get; set; }
[Display(Name = "Password")]
[DisplayName("Enter a password")]
[StringLength(50, MinimumLength = 6, ErrorMessage = "Password must be at least 6 characters long.")]
public string Password { get; set; }
[DisplayName("Confirm Password")]
[StringLength(50, MinimumLength = 6)]
public string PasswordConfirm { get; set; }
#endregion
#region Abstracted Contact Methods
/// <summary>
/// When adding someone, this represents the phone number contact record.
/// </summary>
[Display(Name = "Home Phone Number")]
[DisplayName("Home Phone Number")]
[USPhoneNumber]
//[Required]
public string HomePhoneNumber { get; set; }
/// <summary>
/// When adding someone, this represents the phone number contact record.
/// </summary>
[Display(Name = "Cell Phone Number")]
[DisplayName("Cell Phone Number")]
[USPhoneNumber]
//[Required]
public string CellPhoneNumber { get; set; }
/// <summary>
/// When adding someone, this represents the email address contact record.
/// </summary>
[Display(Name = "Email Address")]
[DisplayName("Email Address")]
//[Required] -- Some parents dont have email addresses
//[UniqueEmailAddress(ErrorMessage = "The email address was already found in our system, please sign in or use another email.")]
[Email(ErrorMessage = "The email address specified isn't an email address.")]
public string EmailAddress { get; set; }
#endregion
#endregion
#region Read-Only Properties
public IList<School> Schools { get; internal set; }
public IList<ContactType> ContactTypes { get; internal set; }
public IList<string> Genders { get; internal set; }
public IList<Grade> Grades { get; internal set; }
public IList<USState> USStates { get; internal set; }
public IList<PersonType> PersonTypes { get; internal set; }
public IList<FamilyRole> FamilyRoles { get; internal set; }
public Person Person { get; internal set; }
#endregion
}
public class EditFamilyMemberService
{
#region Constructor
public EditFamilyMemberService(ITracktionDataLayer dataLayer, MessagePasser messager, AuthUserHelper authUser, int originalPersonID, int familyMemberID)
{
_dataLayer = dataLayer;
_originalPerson = new PersonAddEditModel(messager, dataLayer, authUser, originalPersonID);
_model = new PersonAddEditModel(messager, dataLayer, authUser, familyMemberID);
_grades = _dataLayer.GetGradesForOrganization(authUser.OrganizationID, _model.Person.Grade, true).ToArray();
Model = new EditFamilyMemberModel()
{
Address = _model.Person.Address,
Address2 = _model.Person.Address2,
CellPhoneNumber = _model.PersonalCell,
City = _model.Person.City,
ContactTypes = _model.ContactTypes,
DOB = _model.Person.DOB,
EmailAddress = _model.EmailAddress,
FamilyRoles = _model.FamilyRoles,
FirstName = _model.Person.FirstName,
Gender = _model.Person.Gender,
Genders = _model.Genders,
GradeID = _model.Person.Grade,
Grades = _model.Grades,
HomePhoneNumber = _model.HomePhone,
LastName = _model.Person.LastName,
Password = null,
PasswordConfirm = null,
PersonTypes = _model.PersonTypes,
PictureAction = null,
PictureData = null,
SchoolID = _model.Person.SchoolID,
Schools = _model.Schools,
SpecialNeedsDescription = _model.Person.SpecialNeedsDescription,
SpecialNeedsSummary = _model.Person.SpecialNeedsSummary,
State = _model.Person.State,
Username = null,
USStates = _model.USStates,
Zip = _model.Person.Zip,
Person = _model.Person
};
}
#endregion
#region Private
ITracktionDataLayer _dataLayer;
PersonAddEditModel _originalPerson;
PersonAddEditModel _model;
IList<Grade> _grades;
#endregion
#region Properties
public EditFamilyMemberModel Model { get; internal set; }
#region Read Only
public Person Person
{
get { return _model.Person; }
}
public AuthUserHelper AuthUser
{
get { return _model.AuthUser; }
}
#endregion
#endregion
#region Logic
public void SaveChanges()
{
_model.Person.ChangedTimeStamp = DateTime.Now;
_model.Person.ChangedBy = -1;
// If we have a valid home #, add it
if (!string.IsNullOrWhiteSpace(Model.HomePhoneNumber))
_model.HomePhone = Model.HomePhoneNumber;
// If we have a valid cell #, add it
if (!string.IsNullOrWhiteSpace(Model.CellPhoneNumber))
_model.PersonalCell = Model.CellPhoneNumber;
// If we have a valid email, add it
if (!string.IsNullOrWhiteSpace(Model.EmailAddress))
_model.EmailAddress = Model.EmailAddress;
// Some fields may have 'not applicable'; blank them
Model.SpecialNeedsDescription = Model.SpecialNeedsDescription.NormalizeNotApplicable().NullIfEmptyOrWhitespace();
Model.SpecialNeedsSummary = Model.SpecialNeedsSummary.NormalizeNotApplicable().NullIfEmptyOrWhitespace();
// Names of people should be proper case if they're all entered in lowercase
Model.FirstName = Model.FirstName.NormalizeName();
Model.LastName = Model.LastName.NormalizeName();
// Save this extra information
_model.Person.Address = Model.Address;
_model.Person.Address2 = Model.Address2;
_model.PersonalCell = Model.CellPhoneNumber;
_model.Person.City = Model.City;
_model.Person.DOB = Model.DOB;
_model.EmailAddress = Model.EmailAddress;
_model.Person.FirstName = Model.FirstName;
_model.Person.Gender = Model.Gender;
_model.Person.Grade = Model.GradeID;
_model.HomePhone = Model.HomePhoneNumber;
_model.Person.LastName = Model.LastName;
_model.Person.SchoolID = Model.SchoolID;
_model.Person.SpecialNeedsDescription = Model.SpecialNeedsDescription;
_model.Person.SpecialNeedsSummary = Model.SpecialNeedsSummary;
_model.Person.State = Model.State;
_model.Person.Zip = Model.Zip;
// Save picture data
if (Model.PictureData != null && (Model.PictureAction == "replace" || (Model.PictureAction ?? "") == ""))
{
// Replace just adds, it does not remove the old picture.
Photo pic = PhotoHandling.GetPhotoEntityFromBytes(Model.PictureData);
pic.ROWGUID = Guid.NewGuid();
pic.OrganizationID = _model.AuthUser.OrganizationID;
pic.CreationTimestamp = DateTime.Now;
pic.Deleted = false;
_model.AddNewPhoto(pic);
_model.Person.Avatar = pic.ROWGUID;
}
else if (Model.PictureAction == "remove" && _model.Person.Avatar.HasValue)
{
var pic = _model.Person.Photos.SingleOrDefault(p => p.ROWGUID == _model.Person.Avatar.Value);
if (pic != null)
{
pic.Deleted = true;
_model.Person.Avatar = null;
}
}
_model.SaveChanges();
}
#endregion
}
The model itself does not require any ctor parameters and should not throw that error anymore. I'm a big fan of encapsulating related logic and data into a single class but since this is just a wrapper around the heavy-lifting PersonAddEditModel and it's only used on the MVC side, I can deal with this.
If anyone has any input or alternatives to what I just posted, I'm all ears.