-3

I am trying to pass back a json object back to my action in my device controller, this is then inserted into the database, however when I click the submit button on my form it seems to fire twice. Another problem is that my location field within the json object and pfID does not get sent back to the controller, the other fields get sent back properly. Here is my code:

$('#getDevice').unbind('click').bind('click', function (e) {
    e.stopPropagation();
    //anti forgery token
    //get the form
    var form = $('#addDeviceForm');
    //from the form get the antiforgerytoken
    var token = $('input[name="__RequestVerificationToken"]', form).val();

    var URL = 'Devices/PushIPForCollection';

    //Before this we need to build the model from the input the user has given us
    var device = {
        deviceID: ' ',
        ipAddress: $('#dIPAddress').val(),
        deviceName: $('#dDeviceName').val(),
        CreatedDate: ' ',
        UpdatedDate: ' ',
        CreatedBy: ' ',
        UpdatedBy: ' ',
        deviceSubGroupID: $('#dSubgroup option:selected').val(),
        subgroup: " ",
        companyID: ' ',
        hidden: ' ',
        pfID: $('#dpfID option:selected').val(),
        pf: ' ',
        location: JSON.stringify({
            'region': $('#dRegion').val() == null ? ' ' : $('#dRegion').val(),
            'country': $('#dCountry').val() == null ? ' ' : $('#dCountry').val(),
            'city': $('#dCity').val() == null ? ' ' : $('#dCity').val(),
            'building': $('#dBuilding').val() == null ? ' ' : $('#dBuilding').val(),
            'room': $('#dRoom').val() == null ? ' ' : $('#dRoom').val(),
            'rack': $('#dRack').val() == null ? ' ' : $('#dRack').val()
        })
    };

    alert(device.pfID);
    alert(device.location);

    $.ajax({
        url: URL,
        data: {
            __RequestVerificationToken: token,
            device: device
        },
        type: 'POST',
        success: function (result) {
        },
        error: function (jqXHR, textStatus, errorThrown) {
            alert("An error has occurred please contact us");
        }
    })
    $('#add-device').modal('hide');
    return false;
});

Where I alert my pfID it returns a string of "ba4475ef-0eed-441a-a77e-d733c288bf8e"

Here is my model:

public class Devices
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int deviceID { get; set; }

    [Display(Name="IP Address:"), StringLength(50)]
    public string ipAddress { get; set; }

    [Display(Name = "DeviceName:"), StringLength(50)]
    public string deviceName { get; set; }

    public DateTime? CreatedDate { get; set; }
    public DateTime? UpdatedDate { get; set; }
    public string CreatedBy { get; set; }
    public string UpdatedBy { get; set; }

    [Display(Name="Add to subgroup:")]
    public long? deviceSubGroupID { get; set; }

    [ForeignKey("deviceSubGroupID")]
    public DeviceSubGroup subgroup { get; set; }
    public string companyID { get; set; }
    public string hidden { get; set; }
    public string pfID { get; set; }
    [ForeignKey("pfID")]
    public PfHealth.Pf pf { get; set; }
    public string location { get; set; }
}

and here is my post method:

 public ActionResult PushIPForCollection(Devices device)
    {
        //Before committing to the database we need to check if the device already exists
        var checkIfDeviceExists = db.devices.Any(check => check.ipAddress == device.ipAddress);

        if (!checkIfDeviceExists)
        {
            if (ModelState.IsValid)
            {
                var userID = User.Identity.GetUserId();


                device.CreatedDate = DateTime.Now;
                device.UpdatedDate = DateTime.Now;
                device.CreatedBy = userID;
                device.UpdatedBy = userID;

                var subgroup = db.deviceSubGroups.Where(sub => sub.deviceSubID == device.deviceSubGroupID).FirstOrDefault();
                device.communityString = subgroup.snmpCommunityString;
                device.companyID = subgroup.companyID;

                db.devices.Add(device);
                db.SaveChanges();
            }

        }
        //Can change this to get json in order to let to view know what message to display to the user.
        return RedirectToAction("index");
    }

and here is my form in the view:

<div class="modal fade" id="add-device" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title" id="add-device-Label"><strong>ADD DEVICE</strong></h4><!--add depending on which panel you have clicked-->
        </div>
        <div class="modal-body" id="add-device-body">
            <!--Depending on which panel insert content-->
            @using (Html.BeginForm("PushIPForCollection", "Devices", FormMethod.Post, new { id = "addDeviceForm" }))
            {
                @Html.AntiForgeryToken()

                <hr />
                <div class="form-horizontal">
                    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.LabelFor(m => m.device.ipAddress, new { @class = "col-md-2 control-label"})
                        </div>
                        <div class="col-md-9">
                            @Html.TextBoxFor(m => m.device.ipAddress, new { @class = "form-control", @id = "dIPAddress" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.LabelFor(m => m.device.deviceName, new { @class = "col-md-2 control-label" })
                        </div>
                        <div class="col-md-9">
                            @Html.TextBoxFor(m => m.device.deviceName, new { @class = "form-control", @id = "dDeviceName" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.LabelFor(m => m.device.deviceSubGroupID, new { @class = "col-md-2 control-label" })
                        </div>
                        <div class="col-md-9">
                            @Html.DropDownListFor(m => m.device.deviceSubGroupID, (IEnumerable<SelectListItem>)ViewBag.subGroups, "None", new { @id = "dSubgroup" })
                            @Html.ValidationMessageFor(m => m.device.deviceSubGroupID, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.Label("Region:")
                        </div>
                        <div class="col-md-9">
                            @Html.TextBox("Region", "", new { @class = "form-control", @id = "dRegion" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.Label("Country:")
                        </div>
                        <div class="col-md-9">
                            @Html.TextBox("Country", "", new { @class = "form-control", @id = "dCountry" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.Label("City:")
                        </div>
                        <div class="col-md-9">
                            @Html.TextBox("City", "", new { @class = "form-control", @id = "dCity" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.Label("Building:")
                        </div>
                        <div class="col-md-9">
                            @Html.TextBox("Building", "", new { @class = "form-control", @id = "dBuilding" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.Label("Room:")
                        </div>
                        <div class="col-md-9">
                            @Html.TextBox("Room", "", new { @class = "form-control", @id = "dRoom" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.Label("Rack:")
                        </div>
                        <div class="col-md-9">
                            @Html.TextBox("Rack", "", new { @class = "form-control", @id = "dRack" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-3">
                            @Html.LabelFor(model=>model.device.pfID)
                        </div>
                        <div class="col-md-9">
                            @Html.DropDownListFor(m => m.device.pfID, (IEnumerable<SelectListItem>)ViewBag.pathfinders, "None", new { @id = "dpfID" })
                            @Html.ValidationMessageFor(m => m.device.pfID, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="modal-footer">
                            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                            <button id="getDevice" type="submit" value="CreateGroups" data-loading-text="Loading..." class="btn btn-primary">Save changes</button>
                        </div>
                    </div>
                </div>

            }
        </div>
    </div>
</div>

Johnathon64
  • 1,280
  • 1
  • 20
  • 45
  • Simply use `data: $('#addDeviceForm').serialize(),` to post back your form. If your view is generated correctly by strongly binding to your model, then it will correctly bind to your model. And what is the problem with `pfID` returning `"ba4475ef-0eed-441a-a77e-d733c288bf8e"`? What are you expecting it to be? –  Dec 20 '15 at 00:13
  • \@StephenMuecke Sorry I may not have actually explained properly, its supposed to return that but when I set a breakpoint in the action, it comes back as an empty string along with location. The reason I am doing it this way is because I want to group all the fields within location and store it in the database as JSON – Johnathon64 Dec 20 '15 at 00:14
  • You need to show you model, the view and the POST method. We cannot possibly guess what errors you have (and all you script can be replaced by just a couple of lines of code if you have generated the view correctly) –  Dec 20 '15 at 00:16
  • @StephenMuecke I have updated my post to include the required information you need. – Johnathon64 Dec 20 '15 at 00:24
  • Not sure where you got the idea to create a view like this. First `pfID` should be `Guid` since that's what your posting back. And `location` is typeof `string so why are you trying to construct a complex object? `location` should be a complex object containing properties `string Region`, `string Country` etc. Then change all the helpers to use the `xxxFor()` methods so you strongly bind to your model. And you should remove all the `new { id = xx` code in your helpers - they are not necessary. –  Dec 20 '15 at 00:36
  • And you can simply use `data: $('#addDeviceForm').serialize()` to serialize your form and change the POST in include `[Bind(Prefix = "device")]` to correctly bind the model. And you should be using a view model. Including properties such as `CreatedDate`, `UpdatedBy` etc are not required and should not be in your view. –  Dec 20 '15 at 00:37
  • @StephenMuecke I wrapped that object into a json.stringify I thought a C# type string would be able to accept that – Johnathon64 Dec 20 '15 at 00:43
  • There is too much your doing wrong here. No time at the moment but later today I'll add an answer showing how to do this if no one else does. Can you also confirm what the main form is for (I'm guessing it displays existing devices and you have the dialog to add a new one? –  Dec 20 '15 at 00:51

1 Answers1

1

You do not need to create the javascript object manually, You may use jQuery serialize method to serialize the entire form and send it via ajax as long as the form field names matches with the param names/model property names in the HttpPost action method.

You may create a view model specific to the view.

public class CreateDeviceVm
{
   public string DeviceId {set;get;}
   public string IPAddress {set;get;}
   public int DeviceSubGroupId {set;get;}
   public List<SelectListItem> DeviceSubGroups {set;get;}
   public string City {set;get;}
   public string Room {set;get;}
   //Add properties NEEDED FOR THE VIEW. 
}

In your GET action, create an object of this view model, assign the DeviceSubGroups property and send to the view.

public ActionResult Create()
{
  var vm = new CreateDeviceVm();
  vm.DeviceSubGroups = db.DeviceSubGroups
                  .Select(s=> new SelectLisItem { Value=s.Id.ToString(),
                                                  Text = s.Name }).ToList();

  return View(vm);
}

And in your view, which is strongly typed to the view model, @model CreateDeviceVm

@using(Html.BeginForm())
{
  <label>DeviceId</label>
  @Html.TextBoxFor(s=>s.DeviceId)
  <label>IP Address</label>
  @Html.TextBoxFor(s=>s.IPAddress)
  <label>Sub group</label>
  @Html.DropDownListFor(s=>s.DeviceSubGroups,Model.DeviceSubGroups,"Select")
  <label>City</label>
  @Html.TextBoxFor(s=>s.City)
  <label>Room</label>
  @Html.TextBoxFor(s=>s.Room)
  <input type="submit" id="btnSave" />
}

Now you can add some javascript to listen to the click event on the submit button, get a reference to the form, serialize it and send to the server using jQuery ajax.

$(function(){    
  $("#btnSave").click(function(e){    
    e.preventDefault();

    var _f=$(this).closest("form");
    $.post(_f.attr("action")._f.serialize(),function(res){
        //check res and do something
    });    
  });    
});

And in your HttpPost action method,

[HttpPost]
public ActionResult Create(CreateDeviceVm model)
{
   var exists= db.devices.Any(x=> x.ipAddress == model.ipAddress);
   if (!exists)
   {
      var d= new Device();
      d.IpAddress=model.IPAddress;
      d.DeviceSubGroupId=model.DeviceSubGrouId;
      //Map other properties as well.

      db.Devices.Add(d);
      db.SaveChanges();
      return Json( new { status="success"});
   }
   return Json( new { status="failed"});
}
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • OP is using code such as `@Html.TextBoxFor(m => m.device.ipAddress)` suggesting the main model in the view contains a property for device, so the main model would need to be adjusted to include a property for `CreateDeviceVm`, but then the post method would need a `[Bind(Prefix="...")]` attribute. Suggest the better approach would be a `[ChildActionOnly]` method that returns a partial of the form based on `CreateDeviceVm` and in the main view use `@Html.Action()` to avoid the `BindAttribute` –  Dec 21 '15 at 02:40
  • My answer is suggesting to use a flat view model specific to the view. I am not following what your comment meant for. – Shyju Dec 21 '15 at 03:32
  • It was for OP. I'm guessing that the main view is for displaying a list of devices and the form is for adding a new device. In which case the main view could be `List` and include `@Html.Action()` to render the form. –  Dec 21 '15 at 03:37