0

I'm getting this error intermittently on my MVC 2 web application. My models do not have parameterless constructors, they look like this:

    public AddParentModel(ITracktionDataLayer dataLayer, MessagePasser messager, AuthUserHelper authUser)
    {
        _model = new PersonAddEditModel(messager, dataLayer, authUser, null);
    }

I should note that my controllers do have parameterless constructors. The PersonAddEditModel isn't exposed directly to MVC. The AddParentModel exists only to make MVC binding easier.

Since the AddParentModel (and all other models that MVC is using) exist on the server, I could make them parameterless, although that would mess up my test methods a bit. The weird part about this is that it happens intermittently. I would expect an issue like this to happen consistently but that's definitely not the case - I just get ELMAH sending me an email every other day or so with some unfortunate user getting this error.

I would like to know what my options are. The easiest way I can see of tackling this problem is to create a constructor overload that just sets these parameters to default objects (which I will probably do just to fix the issue at hand for now, but I feel so dirty doing it) but I would like to get input from people on the correct way to handle this. I'm relatively new to MVC. Thanks in advance!

EDIT: I wanted to add that in my [HttpPost] methods where the processing is done, even though I am using a model for MVC's editing on the view, I am receiving back a FormCollection and use that to update the model, rather than have MVC edit the model directly.

    /// <summary>
    /// Add this person, then go to the next page where more people can be added.
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    [HttpPost]
    public ActionResult Index(FormCollection fc, HttpPostedFileWrapper upload) {

        Uri requestUri = !Request.Url.ToString().Contains("/127.0.0.1/") ? Request.Url : new Uri("...");
        MessagePasser messager = new MessagePasser();
        ITracktionDataLayer dataLayer = DataFactory.GetDataLayer();
        AuthUserHelper user = AuthUserHelper.AnonymousWebUserFrom(dataLayer, requestUri);
        AddParentModel model = new AddParentModel(dataLayer, messager, user);

        if (TryUpdateModel(model, fc) && ModelState.IsValid)
        {
        ...

I'm also realizing that, for some models, I cannot create a parameterless constructor because a few models require a couple extra primitives above and beyond my data layer, messaging layer and authenticated user.

tereško
  • 58,060
  • 25
  • 98
  • 150
Derreck Dean
  • 3,708
  • 1
  • 26
  • 45
  • 1
    See http://stackoverflow.com/questions/1355464/asp-net-mvc-no-parameterless-constructor-defined-for-this-object – Artomegus Oct 04 '11 at 15:47

1 Answers1

1

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.

Derreck Dean
  • 3,708
  • 1
  • 26
  • 45
  • Thanks, this has just fixed a problem I was having with a view model giving the same error. Changing my IList properties to internal set did the job! – Electric Sheep Sep 16 '13 at 09:31