views:

51

answers:

3

I'll start off by saying my terminology might be off, which is why I might be having trouble finding an answer to this question, and apologising for the wall of text.

I have a strongly-typed view that inherits from a custom type called Record. Record has a dictionary of named fields of varying datatypes - some strings, some ints, some DateTime/TimeSpans.

Via a dropdownlist onchange on a form, I get to a HttpPost Action method in my controller - as I understand it, the model of the current view is supposed to be 'passed through' to the controller method. It appears to be doing this, but only partially: the DateTime and TimeSpan fields of the Record are coming through as their default values. All the string, int etc fields are coming through fine.

I debugged through the Record code during the databinding that happens in the back end as the HttpPost controller method is called and it seems that a new, blank Record is constructed, then various properties are set - but if as part of constructing a 'blank' record a property is initialised to some valid, non-null value, the Set methods of those properties are never called.

My question is - what's actually happening in this databinding phase? Is it as I described it? Do I have to make all properties of my Record initialise as nulls to get them to databind properly?

Edit: The Record (simplified)

public class Record
{
    public Record() : base()
    {
        fields.Add("Id", new FieldValue { DataType = typeof(int) });     
        fields.Add("StartDateTime", new FieldValue { DataType = typeof(DateTime) });      
    }

    private Dictionary<string, FieldValue> fields = new Dictionary<string, FieldValue>();

    public Dictionary<string, FieldValue> Fields
    {
        get
        {
            return fields;
        }
    }

    public long? Id
    {
        get
        {
            FieldValue fieldValue = Fields["Id"];
            return fieldValue != null ? (long?)fieldValue.Value : null;
        }
        set 
        {
            SetFieldValue("Id", value);
        }
    }

    public DateTime StartDateTime
    {
        get
        {
            FieldValue fieldValue = Fields["StartDateTime"];
            if (fieldValue == null || fieldValue.Value == null)
            {
                return DateTime.MinValue;
            }
            else
            {
                return (DateTime)fieldValue.Value;
            }

        }
        set
        {
            SetFieldValue("StartDateTime", value);
        }
    }

    protected void SetFieldValue(string fieldName, object value)
    {
        Fields[fieldName] = new FieldValue(value);
    }
}

The value class for the dictionary of FieldValues:

public class FieldValue
{
    internal FieldValue(object value)
    {
        DataType = value.GetType();
        Value = value;
    }

    internal FieldValue()
    { }

    public Type DataType { get; set; }  //The data type of the field
    public object Value { get; set; }   //The value of the field
}

I display a strongly-typed record view that shows StartTime in a form, but not Id. When a HttpPost happens, the controller method that handles HttpPosts for that view recieves a record that has the same Id as the original Record, but a StartDateTime of DateTime.MinValue.

Edit2: Does the way I'm displaying any model fields in the view have anything to do with what the controller HttpPost method will 'see' of the model?

A: 

Are you looking for the implementation of the IModelBinder? There is a default IModelBinder

Is this what you were looking for?

PieterG
Sort of? The first of those links describes an example similar to what I'm doing, except in my case (using the MDSN example) the Person object that ends up in the controller method public ActionResult Create(Person person) has the correct ID, but a default Zipcode. If the DefaultModelBinder in the second link is what actually does the binding in the first example, it's all behind the scenes and I can't see which of its methods are being called with what parameters, so can't glean much from it. I'll keep reading.
LMF
A: 

Do you have a form field named StartDateTime? The default model binder matches the id's of the form fields to the property names in the view model. If you don't have a form field named StartDateTime, the model binder will not populate that property.

If you need to track the value but not display it, you can always use a hidden box on the page so that the value can be returned.

Also, it might just be having trouble because of the date and time components and the formatting used in the web page. Scott Hanselman has a blog post that might help.

Jeff Siver
The form fields are named the same as the property names in the model. The odd thing is, one of the properties that IS getting set properly is ID, and despite what I orginally said in the OP (edited out now) this isn't in a form field on the page at all. I'll have a read of the blog post and see if it's a formatting issue, thanks for the links.
LMF
Is the ID property getting populated because of route binding? For example, if your post back URL is something like /controller/action/id, that will cause the ID property to populate in the view model object.
Jeff Siver
Yeah, the route to the Details page is controller/action/id. I don't think this is a date formatting thing as the user doesn't enter the date at all - it's got from a data source as a DateTime object and displayed on the screen in a disabled text field.
LMF
A: 

Turned out it was the combination of a few things:

The view code I was using to display my StartDateTime field was not well structured enough for the default databinder to pick it up.

<input id="StartDateTime" type="text" value="<%= Model.StartDateTime.ToString("g") %>" />

That doesn't work. Neither does this

<input id="StartDateTime" type="text" value="<%= Model.StartDateTime %>" />

This works:

<%= Html.TextBoxFor(model => Model.StartDateTime)%>

The second issue is that my StartDateTime text field was disabled (had the html attribute disabled="disabled") - it's meant to be view only, but disabled fields don't return anything on a post (as mentioned here) so even if I did use the TextBoxFor HtmlHelper, it still wouldn't work. Thanks to everyone for the links, now I know a bit more about what the important attributes are in getting the databinding to work.

LMF