Search code examples
c#asp.net-mvc-4twitter-bootstrap-3razor-2

Problems with input boxs and Complex types


I am trying to make a web page that allows the user to submit information about an object using input boxes and a save button. So far I have found it relatively easy to get the page to work for names, numbers etc. using basic types like strings, integers, booleans and doubles. I can enter this basic information into the input boxes and get the information to save into the database by hitting the save button.

However, the last input field that I need to get working is the Location field. This field is saved as a complex type called LatLng containing Latitude and Longitude values. I have been able to get the input boxes to display properly using the razor HTML form element EditorFor(). It automatically creates two input boxes one for Lat and one for Long.

EX:

enter image description here

But it seems that no matter what I input into these boxes the value stored in the database is always lat:0, long:0.

By setting a break point in my model file I have found out that the by the time it gets to the model the 'value' variable inside the 'set' in Location is already {0,0}.

So my Question is: Is there something wrong with my code that would cause the value passed from the EditorFor() text boxes to be zeroed before reaching my model file? Or is there some way that will allow me to debug this deeper?

This code is from my view page. I removed everything I thought was irrelevant and all of the other fields besides Name and Location, to show one thing that is working for me and the one that is not:

<div class="row">
    <div class="col-md-12">
        <section id="createGenericModelForm">
            @using (Html.BeginForm("Create", "GenericModel", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                <div class="form-group">
                    @Html.LabelFor(m => m.Name, new { @class = "col-md-2 control-label" }) 
                    <div class="col-md-6">
                        @Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
                    </div>      
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Location, new { @class = "col-md-2 control-label" }) 
                    <div class="col-md-6">
                        @Html.EditorFor(m => m.Location, new { @class = "form-control" })
                    </div>      
                </div>                  
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Create" class="btn btn-default" />
                    </div>
                </div>                 
             }        
        </section>
    </div>
</div>              

I also removed everything from the model page besides Name and Location:

public class GenericModel
{
    public GenericModel()
    {           
        Location = new LatLng(0, 0);
    }
    [Required]
    [Display(Name = "Name")]
    public string Name { get; set; }

    private LatLng _Location;

    [Display(Name = "Location")]
    public LatLng Location
    {
        get
        {
            return _Location;
        }
        set
        {
            _Location = value ?? new LatLng(0, 0); //WHEN DEBUGGING THIS IS WHERE value = {0,0}
        }
    }
}

This is the code that defines the Complex Type LatLng:

[ComplexType]
public class LatLng : IEquatable<LatLng>
{
    public LatLng(){ }
    public double Latitude { get; private set; }
    public double Longitude { get; private set; }

    public LatLng(double latitude, double longitude)
    {
        Latitude = Math.Min(Math.Max(-90.0, latitude), 90.0);
        Longitude = Math.Min(Math.Max(-180.0, longitude), 180.0);
    }       
    public override string ToString()
    {
        return string.Format("{0},{1}", Latitude, Longitude);
    }
    public override bool Equals(object obj)     
    {
        return obj is LatLng && this.Equals((LatLng)obj);
    }
    public bool Equals(LatLng other)
    {
        return this.Latitude == other.Latitude
            && this.Longitude == other.Longitude;
    }      
}

I am new to programming in these languages so please be patient with me. Also, please let me know if you required more code.


Solution

  • The problem is the two properties in your LatLng class:

    public double Latitude { get; private set; }
    public double Longitude { get; private set; }
    

    They have a private setter on them.

    I see what you're trying to do, you're trying to set them via a constructor call, but that's not how the model binder works.

    The model binder will do the following when you post that form:

    1. Create a new instance of your model
    2. Latitude and Longitude properties will be set from the values found in the respective form fields.

    If you want to keep your model how it is you'll need to read up on how to create a custom model binder and register it. It's pretty straight forward and you'll have full control.

    Additionally, in your model the Location property doesn't need any implementation for the getter or setter:

    [Display(Name = "Location")]
    public LatLng Location { get; set; }
    

    You're setting the default in your constructor and if Location is null then it just ignores it, it won't set it to null.