views:

152

answers:

3

Hi All,

I have a model object called Problem:

[Table(Name = "Problems")]
public class Problem
{
    [HiddenInput(DisplayValue = false)]
    [Column(IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
    public int ProblemId { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TablePersonStudentName")]
    [Column] public int StudentId { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableCommunicationTypesName")]
    [Column] public int CommunicationTypeId { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemTypeName")]
    [Column] public int ProblemTypeId { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableMitigatingCircumstanceLevelName")]
    [Column] public int MitigatingCircumstanceLevelId { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemDate")]
    [Column] public DateTime? DateTime { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemOutline")]
    [Column] public string Outline { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemFile")]
    [Column] public byte[] MitigatingCircumstanceFile { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemAbsentFrom")]
    [Column] public DateTime? AbsentFrom { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemAbsentUntil")]
    [Column] public DateTime? AbsentUntil { get; set; }

    [Display(ResourceType = typeof(Resources.Resources), Name = "TableProblemRequestedFollowUp")]
    [Column] public DateTime? RequestedFollowUp { get; set; }

    public CommunicationType CommunicationType { get; set; }

    public MitigatingCircumstanceLevel MitigatingCircumstanceLevel { get; set; }

    public ProblemType ProblemCategory { get; set; }

    public ICollection<ProblemCommunication> ProblemCommunications { get; set; }

    public ICollection<AssessmentExtension> AssessmentExtensions { get; set; }

    public ICollection<User> Users { get; set; }

}

As this model contains lots of objects from other database tables I am using dropdownlists in my view by using a viewModel:

public class ProblemViewModel
{
    public Problem Problem { get; set; }
    public SelectList Students { get; set; }
    public SelectList CommunicationType { get; set; }
    public SelectList MitigatingCircumstanceLevel { get; set; }
    public SelectList ProblemType { get; set; }
    public MultiSelectList ProblemUsers { get; set; }

    public ProblemViewModel(Problem problem, ISqlStudentRepository sqlStudentRepository, 
        ISqlCommunicationTypeRepository sqlCommunicationTypeRepository, ISqlMitigatingCircumstanceLevelRepository sqlMitigatingCircumstanceRepository,
        ISqlProblemTypeRepository sqlProblemTypeRepository, ISqlUserRepository sqlUserRepository,
        string username)
    {
        this.Problem = problem;
        this.Students = new SelectList(sqlStudentRepository.Students.ToList(), "StudentId", "FirstName");
        this.CommunicationType = new SelectList(sqlCommunicationTypeRepository.CommunicationTypes.ToList(), "CommunicationTypeId", "Name");
        this.MitigatingCircumstanceLevel = new SelectList(sqlMitigatingCircumstanceRepository.MitigatingCircumstanceLevels.ToList(), "MitigatingCircumstanceLevelId", "Name");
        this.ProblemType = new SelectList(sqlProblemTypeRepository.ProblemTypes.ToList(), "ProblemTypeId", "TypeName");
        this.ProblemUsers = new MultiSelectList(sqlUserRepository.Users.Where(s => s.UserName != username).ToList(), "UserId", "UserName");
    }
}

This is generate upon navigation to the Problem/Create controller method:

public ViewResult Create()
    {
        string username = User.Identity.Name;

        return View("Edit", new ProblemViewModel(new Problem(), sqlStudentRepository, 
            sqlCommunicationTypeRepository, sqlMitigatingCircumstanceRepository,
            sqlProblemTypeRepository, sqlUserRepository, username));
    }

Here is the ascx view:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BournemouthUniversity.WebUI.Models.ProblemViewModel>" %>

<% using (Html.BeginForm("Edit", "Problem", FormMethod.Post, new { id = "editForm" })) {%> <%: Html.ValidationSummary(true) %> <% Html.EnableClientValidation(); %>

        <div class="editor-field">
            <%: Html.HiddenFor(model => model.Problem.ProblemId)%>
            <%: Html.ValidationMessageFor(model => model.Problem.ProblemId)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.StudentId) %>
        </div>
        <div class="editor-field">
            <%: Html.DropDownListFor(model => model.Problem.StudentId, Model.Students)%>
            <%: Html.ValidationMessageFor(model => model.Problem.StudentId)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.CommunicationTypeId)%>
        </div>
        <div class="editor-field">
            <%: Html.DropDownListFor(model => model.Problem.CommunicationTypeId, Model.CommunicationType)%>
            <%: Html.ValidationMessageFor(model => model.Problem.CommunicationTypeId)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.ProblemTypeId)%>
        </div>
        <div class="editor-field">
            <%: Html.DropDownListFor(model => model.Problem.ProblemTypeId, Model.ProblemType)%>
            <%: Html.ValidationMessageFor(model => model.Problem.ProblemTypeId)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.MitigatingCircumstanceLevelId)%>
        </div>
        <div class="editor-field">
            <%: Html.DropDownListFor(model => model.Problem.MitigatingCircumstanceLevelId, Model.MitigatingCircumstanceLevel)%>
            <%: Html.ValidationMessageFor(model => model.Problem.MitigatingCircumstanceLevelId)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.DateTime)%>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Problem.DateTime, new { @class = "datePicker" })%>
            <%: Html.ValidationMessageFor(model => model.Problem.DateTime)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.Outline)%>
        </div>
        <div class="editor-field">
            <%: Html.TextAreaFor(model => model.Problem.Outline, 6, 70, new { maxlength = 255 })%>
            <%: Html.ValidationMessageFor(model => model.Problem.Outline)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.AbsentFrom)%>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Problem.AbsentFrom, new { @class = "datePicker" })%>
            <%: Html.ValidationMessageFor(model => model.Problem.AbsentFrom)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.AbsentUntil)%>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Problem.AbsentUntil, new { @class = "datePicker" })%>
            <%: Html.ValidationMessageFor(model => model.Problem.AbsentUntil)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.RequestedFollowUp)%>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Problem.RequestedFollowUp, new { @class = "dateTimePicker" })%>
            <%: Html.ValidationMessageFor(model => model.Problem.RequestedFollowUp)%>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(model => model.Problem.Users)%>
        </div>
        <div class="editor-field">
            <%: Html.ListBoxFor(model => model.Problem.Users, Model.ProblemUsers, new { @class = "multiselect" })%>
            <%: Html.ValidationMessageFor(model => model.Problem.Users)%>
        </div>

        <p>
            <input type="submit" class="button" value="Save" />
        </p>

<% } %>

However when I submit the form the [HttpPost] Edit controller action is entered but with null for the majority of values...

[HttpPost]
    public ActionResult Edit(Problem problemValues)
    {
        try
        {
            MembershipUser myObject = Membership.GetUser();
            String UserId = myObject.ProviderUserKey.ToString();

            Problem problem = problemValues.ProblemId == 0
                ? new Problem()
                : sqlProblemRepository.Problems(UserId).First(p => p.ProblemId == problemValues.ProblemId);
            TryUpdateModel(problem);

            if (ModelState.IsValid)
            {
                sqlProblemRepository.SaveProblem(problem);
                TempData["message"] = problem.ProblemId + " has been saved.";

                if (Request.IsAjaxRequest())
                {
                    return Json(problem);
                }

                return RedirectToAction("Details", "Student", new { problem.StudentId });
            }
            else
                return View(problem);
        }
        catch (Exception ex)
        {
            if (Request.IsAjaxRequest())
            {
                return Json(null);
            }
            else
            {
                TempData["message"] = "Record Not Found.";
                return RedirectToAction("Index");
            }
        }
    }

alt text

Any Ideas on this would be appreciated it appears to happen on most of my forms where I have dropdowns however I don't understand why all the values are null even the non-dropdown fields.

Thanks in advance...

Jonathan

A: 

I think you need to change the signature of your Edit action to

[HttpPost]
public ActionResult Edit(int problemId, Problem problemValues)
{
.
.
}

it seems you have the same problem as mention here http://stackoverflow.com/questions/765406/mvc-custom-viewmodel-and-auto-binding

ajay_whiz
+1  A: 

if you check the html generated, I think you will find the form fields are using a dot notation..basically posting back model.Problem instead of just Problem... Therein lies your...uh....problem

edit I did not do a great job explaining this I think....your html fields should be posting back properties that will map to the model that is accepted by the action, in this case the action is expecting a Problem model....however your html fields are posting back a model that HAS A Problem...rather than IS A Problem.

E Rolnicki
I checked my HTML and I dont think I have model.Problem:<input id="Problem_ProblemId" name="Problem.ProblemId" type="hidden" value="0" /><select id="Problem_StudentId" name="Problem.StudentId"><option value="2">Student</option><select id="Problem_CommunicationTypeId" name="Problem.CommunicationTypeId">All are Problem.
Jonathan Stowell
on the contrary, that is your problem. those fields, in order to map properly, should be just ProblemId, StudentId, CommunicationTypeId, etc...instead of Problem.XXXX
E Rolnicki
the solution is to use the other Htmlhelper overloads, so insetad of <%: Html.HiddenFor(model => model.Problem.ProblemId)%> you would use <%: Html.Hidden("ProblemId",Model.Problem.ProblemId)%>
E Rolnicki
Thanks for the help. Whilst Darin Dimitrov's solution did work it was much easier to send the view model to the view but use your solution in the view to return a problem object and not viewmodel object. So I have used advice from both of you. I've taken the repositories out of my viewmodels and now get the data in the controller calling the viewmodel constructor after. Thanks for the advice appreciate it!
Jonathan Stowell
+2  A: 

I would recommend you to keep your repositories separate from the model. This way all you pass to the view is the model. Neither the View nor the ViewModel should need any repository. The way it works is the controller uses a repository to fetch the model and pass this model to the view:

public ViewResult Create()
{
    string username = User.Identity.Name;
    Problem model = someRepository.FetchModel(username);
    ProblemViewModel viewModel = someMapper.ConvertToViewModel(model);

    return View("Edit", viewModel);
}

And the submit action:

[HttpPost]
public ViewResult Create(ProblemViewModel viewModel)
{
    // viewModel will contain all the fields that you have corresponding
    // inputs in the View
    ...
}
Darin Dimitrov
If I change my Controller action header to [HttpPost]public ViewResult Edit(ProblemViewModel viewModel) I get: No parameterless constructor defined for this object.
Jonathan Stowell
Yes, that's normal. You have to remove your constructor that takes all the repositories as it makes no longer any sense.
Darin Dimitrov
while this did provide a solution as to 'rewrite everything to make it more like X' this is not the solution to the question you posted...
E Rolnicki
Thanks for the help. This solution did work however I found it easier to send the view model to the view but use E Rolnicki solution in the view to return a problem object to the Edit action and not viewmodel object. I've taken the repositories out of my viewmodels and now get the data in the controller calling the viewmodel constructor after. Thanks for the advice appreciate it!
Jonathan Stowell
@E Rolnicki, SO is not only for giving solutions to questions, it is for advocating good practices and passing repositories to views and using non-strongly typed helpers is definitely not a good practice.
Darin Dimitrov
@ Darin Dimitrov, tying yourself to those particular html helpers does not give you the flexibility to include more robust models, since it will automagically dump out the entire model hiearchy for a property. it forces you to break DRY principles. the question was not 'how can this be a better design' but rather 'why is my model not binding'
E Rolnicki