Search code examples
apiasp.net-mvc-4knockout-mvc

POST model using KnockoutJS is different from API model


I have an issue with the model on post. The collection of 'EmployeeDetailses' is null when the model is posted using Ajax calls. I checked it with Fiddler but the JSON model is actually good. The problem is with the model passed as parameter to controller.

var EmployeeViewModel = function () {
    // Make the self as 'this' reference
    var self = this;
    // Declare observable which will be bind with UI
    self.EmployeeId = ko.observable("");
    self.FirstName = ko.observable("");
    self.MiddleName = ko.observable("");
    self.Tussenvoegsel = ko.observable("");
    self.LastName = ko.observable("");
    self.FullName = ko.observable("");
    self.EmployeeEmail = ko.observable("");
    self.PersonalNumber = ko.observable("");
    self.PhoneNumber = ko.observable("");
    
    //The Object which stored data entered in the observables
    var EmployeeData = {
        EmployeeId: self.EmployeeId,
                FirstName: self.FirstName,
        MiddleName: self.MiddleName,
        Tussenvoegsel: self.Tussenvoegsel,
        LastName: self.LastName,
        FullName: self.FullName,
        EmployeeEmail: self.EmployeeEmail,
        EmployeeDetailses: {
            PhoneNumber: self.PhoneNumber,
            PersonalNumber: self.PersonalNumber
        }
    };

    // Declare an ObservableArray for Storing the JSON Response
    self.Employees = ko.observableArray([]);

    GetEmployees(); // Call the Function which gets all records using ajax call

    // Function to Read All Employees
    function GetEmployees() {
        // Ajax Call Get All Employee Records
        $.ajax({
            type: "GET",
            url: "/api/EmployeeApi",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (data) {
                self.Employees(data); // Put the response in ObservableArray
            },
            error: function (error) {
                alert(error.status + "<--and--> " + error.statusText);
            }
        });
        // Ends Here
    }

    // Function to Display record to be updated
    self.getEmployeeDetails = function (employee) {
        self.EmployeeId(employee.EmployeeId),
        self.FirstName(employee.FirstName),
        self.MiddleName(employee.MiddleName),
        self.Tussenvoegsel(employee.Tussenvoegsel),
        self.LastName(employee.LastName),
        self.FullName(employee.FullName),
        self.EmployeeEmail(employee.EmployeeEmail),
        self.PersonalNumber(employee.EmployeeDetailses[0].PersonalNumber),
        self.PhoneNumber(employee.EmployeeDetailses[0].PhoneNumber)
    };

    // Function to perform POST (insert Employee) operation
    self.save = function () {
        // Ajax call to Insert the Employee

        $.ajax({
            type: "POST",
            url: "/api/EmployeeApi",
            contentType: "application/json",
            dataType: "json",
            data: ko.mapping.toJSON(EmployeeData), // Convert the Observable Data into JSON
            success: function () {
                alert("Record Added Successfully");
                GetEmployees();
            },
            error: function () {
                alert("Failed");
            }
        });
        // Ends Here
    };
};
ko.applyBindings(new EmployeeViewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
@using Resources
@model dynamic

@{
    ViewBag.Title = "Index";
}

<html>
<head>
    <title>Employee Index</title>
    <link href="~/Content/Site.css" rel="stylesheet" />
</head>
<body>
    <form>
        <div id="container">
            <!--Bind the TextBoxes in the Table to the observable properties defined into the ViewModel -->
            <table id="tblEmployeeView">
                <tr>
                    <td>
                        <label id="lblFirstName">@Resource.FirstName</label>
                    </td>
                    <td>
                        <input type="text" id="txtFirstName" data-bind="value: $root.FirstName" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <label id="lblMiddleName">@Resource.MiddleName</label>
                    </td>
                    <td>
                        <input type="text" id="txtMiddleName" data-bind="value: $root.MiddleName" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <label id="lblNamePrefix">@Resource.NamePrefix</label>
                    </td>
                    <td>
                        <input type="text" id="txtNamePrefix" data-bind="value: $root.Tussenvoegsel" />
                    </td>
                    <td>
                        <label id="lblLastName">@Resource.LastName</label>
                    </td>
                    <td>
                        <input type="text" id="txtLastName" data-bind="value: $root.LastName" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <label id="lblEmail">@Resource.Email</label>
                    </td>
                    <td>
                        <input type="text" id="txtEmail" data-bind="value: $root.EmployeeEmail" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <label id="lbPersonalNo">@string.Format("{0} {1}", Resource.Personal, Resource.Number.ToLower())</label>
                    </td>
                    <td>
                        <input type="text" id="txtPersonalNo" data-bind="value: $root.PersonalNumber" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <label id="lbPhoneNo">@string.Format("{0} {1}", Resource.Phone, Resource.Number.ToLower())</label>
                    </td>
                    <td>
                        <input type="text" id="txtPhoneNo" data-bind="value: $root.PhoneNumber" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <button data-bind="click: $root.save">Save new</button>
                    </td>
                </tr>
            </table>
            <div class="Fixed">
                <table id="tblEmployees" data-bind="visible: Employees().length>0" style="border: double">
                    <thead>
                        <tr>
                            <td>Full name</td>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach: Employees">
                        <tr id="objEmployee" data-bind="click: $root.getEmployeeDetails" style="cursor: pointer">
                            <td><span data-bind="text:  $root.FullName"></span></td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </form>
    <script src="~/Scripts/jquery-1.11.2.js"></script>
    <script src="~/Scripts/knockout-3.0.0.js"></script>
    <script src="~/Scripts/knockout.mapping-latest.debug.js"></script>
    <script src="~/Scripts/ViewScripts/Employee/EmployeeViewModel.js"></script>
</body>
</html>

Here is the code on the controller:

public HttpResponseMessage PostEmployee(Employee employee)
        {
            // Validate model
            if (!ModelState.IsValid) return Request.CreateResponse(HttpStatusCode.BadRequest);
            try
            {
                // Add employee 
                UoW.Empleyee.Add(employee);
                // Save the new employee
                UoW.Commit();
                // Log change
                ApplicationLog.LogChange(new UpdateException("New employee created by userId:" + Globals.Employee.EmployeeId), "Employee id:" + employee.EmployeeId + " created successfully.");
            }
            catch (Exception ex)
            {
                // Log error
                ApplicationLog.LogError(ex, "UserId:" + Globals.Employee.EmployeeId + " failed adding a new employee.");
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Conflict));
            }

            // Create response with the new created model
            var response = Request.CreateResponse(HttpStatusCode.Created, employee);
            response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = employee.EmployeeId }));
            return response;
        }

The model is:

public class Employee : EntityBase, IValidatableObject
    {
        #region Properties

        // Self generated field for Id
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int EmployeeId { set; get; }

        // Company mandatory
        [Required]
        public int CompanyId { get; set; }
        [ForeignKey("CompanyId")]
        public Company Company { get; set; }

        // First name mandatory, max length 75
        [Required, StringLength(75)]
        public string FirstName { get; set; }

        // Middle name not mandatory, max length 75
        [StringLength(75)]
        public string MiddleName { get; set; }

        // Last name mandatory, max length 75
        [Required, StringLength(75)]
        public string LastName { get; set; }

        // Tussenvoegsel (example: van de) not mandatory, max length 15
        [StringLength(15)]
        public string Tussenvoegsel { get; set; }

        // Email addressa mandatory, max length 150
        [Required, StringLength(150)]
        public string EmployeeEmail { get; set; }

        // Object status mandatory 
        [Required]
        public int ObjectStatusId { get; set; }
        [ForeignKey("ObjectStatusId")]
        public ObjectStatus ObjectStatus { get; set; }

        // Enumerable collection for employee details
        public virtual ICollection<EmployeeDetails> EmployeeDetailses { get; set; } 

        #endregion

        #region Custom properties

        // Full name of employee
        public string FullName
        {
            get
            {
                // E.g: Andrew, Marteen, de Fulshrad
                if (!string.IsNullOrEmpty(MiddleName) && !string.IsNullOrEmpty(Tussenvoegsel))
                    return string.Format("{0}, {1}, {2} {3}", FirstName, MiddleName, Tussenvoegsel, LastName);
                // E.g: Andrew, Marteen, Fulshrad
                if (!string.IsNullOrEmpty(MiddleName) && string.IsNullOrEmpty(Tussenvoegsel))
                    return string.Format("{0}, {1}, {2}", FirstName, MiddleName, LastName);
                // E.g: Andrew,de Fulshrad
                if (string.IsNullOrEmpty(MiddleName) && !string.IsNullOrEmpty(Tussenvoegsel))
                    return string.Format("{0}, {1} {2}", FirstName, Tussenvoegsel, LastName);
                // E.g: Andrew, Fulshrad
                if (string.IsNullOrEmpty(MiddleName) && string.IsNullOrEmpty(Tussenvoegsel))
                    return string.Format("{0}, {1}", FirstName, LastName);
                return null;
            }
        }

What am i doing wrong?


Solution

  • Found the issue. The model was not parsed properly due to the fact that i forgot about the '[ ]' for the collection

    var EmployeeData = {
            EmployeeId: self.EmployeeId,
                    FirstName: self.FirstName,
            MiddleName: self.MiddleName,
            Tussenvoegsel: self.Tussenvoegsel,
            LastName: self.LastName,
            FullName: self.FullName,
            EmployeeEmail: self.EmployeeEmail,
            EmployeeDetailses: [{
                PhoneNumber: self.PhoneNumber,
                PersonalNumber: self.PersonalNumber
            }]
        };