0

My problem is that when I send a complete Javascript object to my Web API Controller I always get null values. Even though I have a complete object I need to specify each attribute and value as you can see below. How can I make Web API accept a ready made Javascript object and bind it correctly?

C# Web Api Controller:

[Route("addcredentials/{salesId}")]
[HttpPost]
public IHttpActionResult AddCredentials([FromUri] int salesId, [FromBody] ScriveCredentials credentials)
{
    return Ok(credentials);
}

C# Credentials object:

public class Credentials
{
    public string ClientIdentifier { get; set; }

    public string ClientSecret { get; set; }

    public string TokenIdentifier { get; set; }

    public string TokenSecret { get; set; }
}

Javascript object passed to resource, saved as "result" further down:

{ClientIdentifier: "a", ClientSecret: "b", TokenIdentifier: "c", TokenSecret: "d"}

Resource method:

addCredentials: {
    method: 'POST',
    url: 'api/addcredentials/:userSalesId'
}

Usage that results in null values:

userResource.addCredentials({ userSalesId: user.SalesId }, { credentials: result}).$promise.then(function (data) {
    console.log(data);
});

Payload for this request:

{"credentials":{"ClientIdentifier":"a","ClientSecret":"b","TokenIdentifier":"c","TokenSecret":"d"}}

Usage that works but seems overly complicated:

userResource.addCredentials({ userSalesId: user.SalesId }, { ClientIdentifier: result.ClientIdentifier, ClientSecret: result.ClientSecret, TokenIdentifier: result.TokenIdentifier, TokenSecret: result.TokenSecret }).$promise.then(function (data) {
    console.log(data);
});

Request payload:

{"ClientIdentifier":"a","ClientSecret":"b","TokenIdentifier":"c","TokenSecret":"d"}

Update

Tried the following and it did not work either, null on all values:

addScriveCredentials: {
    method: 'POST',
    url: 'api/addcredentials/'

result.SalesId = user.SalesId;
userResource.addCredentials({}, { credentials: result }).$promise.then(function (data) {
    console.log(data);
});

C#:

[Route("addcredentials")]
[HttpPost]
public IHttpActionResult Addcredentials(Credentials credentials)
{
    return Ok(credentials);
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418

2 Answers2

0

I had done a sample earlier, hope the follwing code will help you to understand.

The Model.

using System.ComponentModel.DataAnnotations;

public class ProductModel
{
    public ProductModel(int id, string name, string category, decimal price)
    {
        Id = id;
        Name = name;
        Category = category;
        Price = price;
    }

    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Category { get; set; }

    [Required]
    public decimal Price { get; set; }
}

The ApiController.

using Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Hosting;
using System.Web.Http;

public class ProductsController : ApiController
{
    // other code omitted for brevity.

    [HttpPost]
    public IHttpActionResult PostProduct(ProductModel product)
    {
        try
        {
            if (product == null)
            {
                throw new ArgumentNullException("Product parameter cannot be null");
            }

            if (ModelState.IsValid)
            {
                // code omitted for brevity.

                return this.Ok();
            }
            else
            {
                throw new Exception("Product is invalid");
            }
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }

    [HttpPut]
    public IHttpActionResult PutProduct(ProductModel product)
    {
        try
        {
            if (product == null)
            {
                throw new ArgumentNullException("Product parameter cannot be null");
            }

            if (ModelState.IsValid && product.Id > 0)
            {
                // code omitted for brevity.

                return this.Ok();
            }
            else
            {
                throw new Exception("Product is invalid");
            }
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }

    // other code omitted for brevity.

}

The Angular service.

// products.js

(function () {
    "use strict";

    angular
        .module("exampleApp")
        .constant("baseUrl", "http://localhost:53631/api/products/")
        .factory("productsResource", productsResource);

    productsResource.$inject = ["$resource", "baseUrl"];

    function productsResource($resource, baseUrl) {
        return $resource(baseUrl + ":id",
            {
                id: "@id"
            },
            {
                create: {
                    method: "POST"
                },
                save: {
                    method: "PUT"
                }
            });
    }
})();

The Angular controller. Focus on createProduct and updateProduct functions below

// edit.controller.js

(function () {
    "use strict";

    angular
        .module("exampleApp")
        .controller("EditController", EditController);

    EditController.$inject = ["$routeParams", "$location", "productsResource"];

    function EditController($routeParams, $location, productsResource) {
        var vm = this;
        vm.currentProduct = null;
        vm.createProduct = createProduct;
        vm.updateProduct = updateProduct;
        vm.saveEdit = saveEdit;
        vm.cancelEdit = cancelEdit;

        if ($location.path().indexOf("/edit/") === 0) {
            var id = $routeParams.id;
            productsResource.get({ id: id }, function (data) {
                vm.currentProduct = data;
            });
        }

        function cancelEdit() {
            $location.path("/list");
        }

        function updateProduct(product) {
            product.$save().then(function () {
                $location.path("/list");
            });
        }

        function saveEdit(product) {
            if (angular.isDefined(product.id)) {
                vm.updateProduct(product);
            } else {
                vm.createProduct(product);
            }

            vm.currentProduct = {};
        }

        function createProduct(product) {
            new productsResource(product).$create().then(function (newProduct) {
                $location.path("/list");
            });
        }
    }
})();
Abdul Mateen Mohammed
  • 1,864
  • 1
  • 12
  • 21
0

You could pass one object as the body of the post message instead.

Add a class like this

public class addCredentialObj
{
    public int salesId { get; set; }
    public Credentials credentials { get; set; }
}

Modify your controller like this (The use of FromBody can be read here)

[Route("addcredentials")]
[HttpPost]
public IHttpActionResult AddCredentials([FromBody]addCredentialObj obj)
{
    return Ok(credentials);
}

In the client you need to create a matching json object to the addCredentialObj class

var yourCredentials = {"ClientIdentifier":"a","ClientSecret":"b","TokenIdentifier":"c","TokenSecret":"d"};
var jsonData = {
    salesId: yourId,
    credentials: yourCredentials
};

And then in the $http request to the controller, stringify the json object

$http({
    method: 'POST',
    url: 'api/addcredentials',
    headers: {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/json'
    },
    data: JSON.stringify(jsonData),
});
Community
  • 1
  • 1
Marcus Höglund
  • 16,172
  • 11
  • 47
  • 69
  • I'm sending one parameter in the URL and the rest is payload, in your example above it would just be like adding a parameter to the URL? I have been able to do this before with [FromBody] for the complex object and [FromUri] for simple type in URL parameter but it does not work here for some reason. – Ogglas Jul 06 '16 at 09:08
  • @Ogglas In your controller, you do not specify what type of parameters they are and then its only possible to pass one parameter as a post message can only have one body. When it worked last time, did you pass multiple parameters then? You could combine [FromBody] and [FromUri] http://stackoverflow.com/questions/12072277/reading-fromuri-and-frombody-at-the-same-time – Marcus Höglund Jul 06 '16 at 09:18
  • Yes it does that automatically. I tried with [FromBody] and [FromUri] but I got the same result. "By default, Web API uses the following rules to bind parameters: If the parameter is a “simple” type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string. (More about type converters later.) For complex types, Web API tries to read the value from the message body, using a media-type formatter." – Ogglas Jul 06 '16 at 09:23
  • http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api – Ogglas Jul 06 '16 at 09:23
  • @Ogglas Ok, After you've updated the question I think it looks ok on the controller side. What Content-type are you sending the request with? – Marcus Höglund Jul 06 '16 at 09:29
  • Content-Type:application/json;charset=UTF-8 – Ogglas Jul 06 '16 at 09:30
  • @Ogglas are you stringifying the Credentials object? – Marcus Höglund Jul 06 '16 at 09:32
  • No but looking at the payload it looks like a complete json-object? "{"credentials":{"ClientIdentifier":"a","ClientSecret":"b","TokenIdentifier":"c","TokenSecret":"d"}}". I tried to stringify it but then it just added another layer to the object. – Ogglas Jul 06 '16 at 09:38
  • I also tried sending everything in one object like you said, still null values. – Ogglas Jul 06 '16 at 09:39
  • Body data is this: "{ "credentials": { "ClientIdentifier": "a", "ClientSecret": "b", "TokenIdentifier": "c", "TokenSecret": "d" } }" According to http://jsonlint.com/ it is correct json. – Ogglas Jul 07 '16 at 05:38
  • @Ogglas Ok, as you are expecting only a Credentials object as parameter and not an object with a parameter of Credentials the correct way should be only { "ClientIdentifier": "a", "ClientSecret": "b", "TokenIdentifier": "c", "TokenSecret": "d" } as a body. Could you try that? – Marcus Höglund Jul 07 '16 at 05:42