POST 2 Guid parameters to API with Angular - c#

I Have made an API to follow a user. The method accepts 2 parameters which are two guids. The method:
// Follow user
[HttpPost]
public async Task<ActionResult<Guid>> FollowUser([FromBody] Guid user_gd, Guid user2_gd)
{
if (ModelState.ErrorCount > 0)
{
return BadRequest();
}
var followedUser = await _user.FollowUser(user_gd, user2_gd);
return Ok(followedUser);
}
The manager in API:
public async Task<bool> FollowUser(Guid user_gd, Guid user2_gd)
{
var followUserQuery =
#"
insert into userbind(gd, user_gd, followed_user_gd, date_followed)
values(#_gd, #_user_gd, #_followed_user_gd, #_date_followed)
";
await PostQuery(followUserQuery, new
{
_gd = GenerateGd(),
_user_gd = user_gd,
_followed_user_gd = user2_gd,
_date_followed = DateTime.Now
});
return true;
}
The API request in Angular (service):
followUser(followed_user, user_gd): Observable<any> {
try {
return this._http.post<any>(this._apiUrl + "FollowUser", { "user2_gd": followed_user, "user_gd": user_gd }, this.httpOptions);
} catch (e) {
console.log("POST error: ", e);
}
}
The component:
followUser(gd) {
console.log(gd);
this._userService.followUser(gd, localStorage.getItem("gd")).subscribe(
res => {
console.log(res);
}
)
}
The variables and everything works right now but I am getting this error everytime:\
"Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Guid' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'user2_gd', line 2, position 15."
Does someone know how to fix this or expierences the same problem? Please reach out.

Try creating class with required fields and accepting it in your FollowUser action as a parameter:
public class FollowUserParams
{
public Guid user_gd { get; set; }
public Guid user2_gd { get; set; }
}
// Follow user
[HttpPost]
public async Task<ActionResult<Guid>> FollowUser([FromBody] FollowUserParams p)
{
... use p
}
Also see Andrew Lock's post on model binding in ASP.

Related

Deserialize JSON into any object

I have an API request that goes off and the response structure back looks like this:
{
"MessageBody": {
"foo" : ""
}
}
The properties under MessageBody can be anything, not only foo, but its value is always a string.
eg. {"MessageBody": { "Token": "abc" }} or {"MessageBody": { "Name": "abc" }}
How can I capture this response from the API as a generic object for the property under MessageBody?
I can represent the first example above as:
public class MessageBody
{
public string Token { get; set; }
}
How would I represent both Token or Name properties under the same MessageBody object? There's a bunch of different values that MessageBody can have, but again they would all be of type string.
I have acheived something similar using Newtonsoft
Your route should take the body in as a generic object and it can then be deserialized into any object you'd like:
/*Using this method, the controller will automatically handle
validating proper Json format*/
[HttpPost]
public async Task<IActionResult> Post([FromBody] object Body)
{
/*here you will send the generic object to a service which will deserialize.
the object into an expected model.*/
customService.HandlePost(Body);
}
Now create an object with any expected fields you would get from the body. (Json2csharp.com is extremely useful!)
public class MessageBody
{
public string Token { get; set; }
public string Name { get; set; }
}
Inside your service you can handle the object like this:
using Newtonsoft.Json
using Models.MessageBody
public class customService()
{
public void HandlePost(object body)
{
var DeserializedBody = JsonConvert.DeserializeObject<MessageBody>(body);
//Any Values that were not assigned will be null in the deserialized object
if(DeserializedBody.Name !== null)
{
//do something
}
}
}
This is obviously a very bare bones implementation, error handling will be important to catch any invalid data. Instead of using one object and null fields to get the data you need, I would recommend adding a "subject" route variable (string) that you can use to determine which object to deserialize the body into.
post *api/MessageBody/{Subject}

Receiving arbitrary JSON object in MVC method

I have a C# view class such as this:
public class DataObject
{
public int Number { get; set; }
public dynamic Data { get; set; } // <-----
}
being used in an MVC method like this
[HttpPost]
public ActionResult SaveData(DataObject request) {}
The problem is that I want to recieve multiple types of objects in the Data property of the DataObject class.
That is, I want both these to work as valid input json objects.
Type 1
{
Number: 1,
Data: {
Text: "a text"
}
}
Type 2
{
Number: 2,
Data: {
Value: 1,
Options: { 1, 2, 3, 4 }
}
}
Is there a way of doing this with either dynamic objects or some other type of json library magic (just making the property dynamic did nothing)?
All i want to do is store this data in a SQL column nvarchar field and return at a later time (through Entity Framework).
An alternate solution would be to create a view model for each type of input but as there will be 100's of variants to it creating all these views and the corresponding input methods would be cumbersome to maintain.
Adding more details as per comment request: The method is called through Angular.
pub.Save = function (jsonData) {
return $http(
{
method: "POST",
url: baseURL + "/Save",
data: { request: jsonData}, // tried this for string
// data: jsonData, // original way
timeout: 30000
}
)
.then(function (result) {
return result.data;
});
}
At the server side, DTO class must match with the same property name which the payload is carrying.
public class DataObject
{
public string test { get; set; } // <-----
}
So, your save method remains the same:
[HttpPost]
public ActionResult SaveData(DataObject request) {}
The payload json is in the object request.test but its seralized.
Deseralize it using Json Library.
How is it handling multiple different types of variables?
Deseralize it to a dynamic type as:
dynamic obj = JsonConvert.DeserializeObject(request.test, typeof(object));
//Properties within the obj are checked at run time.
if(obj.Text != null) {
//Do your thing
}
if(obj.Value != null) {
//Do your thing
}
if(obj.Options != null) {
//Do your thing
}
By converting the data to a JSON string on the client side I was able to send it to the string property and thus being able to use the same typed view for all objects.
I ended up doing this when saving the object (I'm using angular on the front end), converting the Json object to a string.
entry.Data = angular.toJson(entryData.Data, false);
And then when getting the json string back from MVC I did this to get it back to a real javascript object.
entry.Data = angular.fromJson(entry.Data);
MVC would not accept the JSON object into the text property without making it into a json string first.
Using the above method I am storing data like this in my database:
"{\"Value\":123,\"Currency\":\"EUR\"}"

Mvc-style parameter binding in Web Api 2?

I am trying to use both FromUri and FromBody in web api 2 to populate an incoming request model. I understand I need to write a custom model binder to do that. Here is the example everyone references. This solution has been incorporated into the WebAPIContrib nuGet pacakge whose source code can be seen here on github.
I'm having trouble getting the MvcActionValueBinder to work with application/json body content. Here is part of the source that is throwing the exception.
class MvcActionBinding : HttpActionBinding
{
// Read the body upfront , add as a ValueProvider
public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
HttpContent content = request.Content;
if (content != null)
{
FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
if (fd != null)
{
IValueProvider vp = new NameValuePairsValueProvider(fd, CultureInfo.InvariantCulture);
request.Properties.Add(Key, vp);
}
}
return base.ExecuteBindingAsync(actionContext, cancellationToken);
}
}
This line is throwing the exception:
FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
Here is the exception:
System.AggregateException
{"Cannot deserialize the current JSON object (e.g.
{\"name\":\"value\"}) into type
'System.Net.Http.Formatting.FormDataCollection' because the type
requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo
fix this error either change the JSON to a JSON array (e.g. [1,2,3])
or change the deserialized type so that it is a normal .NET type (e.g.
not a primitive type like integer, not a collection type like an array
or List) that can be deserialized from a JSON object.
JsonObjectAttribute can also be added to the type to force it to
deserialize from a JSON object.\r\nPath 'creditLimit', line 2,
position 17."}
How can I get the model binder to work with applciation/json content instead of x-www-form-urlencoded content? Here is a similar question with no answer on the asp.net forums.
Update:
Here is the controller method:
[Route("{accountId:int}/creditlimit")]
[HttpPut]
public async Task<IHttpActionResult> UpdateAccountCreditLimit(int accountId, [FromBody] RequestObject request)
{
// omitted for brevity
}
Here is the RequestObject:
class RequestObject
{
public int AccountId { get; set; }
public decimal CreditLimit { get; set; }
}
Here is the postman endpoint to test, its a PUT:
http://localhost/api/accounts/47358/creditlimit
The body I have set to application/json. Here is sample content.
{ "creditLimit": 125000.00 }
And yes, I realize I could change the controller method to do all FromUri or all FromBody instead. I do not have the liberty of doing that. Thanks.
I had the same issue and I think I finally figure this out.
Here is the code :
internal sealed class MvcActionValueBinder : DefaultActionValueBinder
{
private static readonly Type stringType = typeof(string);
// Per-request storage, uses the Request.Properties bag. We need a unique key into the bag.
private const string Key = "5DC187FB-BFA0-462A-AB93-9E8036871EC8";
private readonly JsonSerializerSettings serializerSettings;
public MvcActionValueBinder(JsonSerializerSettings serializerSettings)
{
this.serializerSettings = serializerSettings;
}
public override HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor)
{
var actionBinding = new MvcActionBinding(serializerSettings);
HttpParameterDescriptor[] parameters = actionDescriptor.GetParameters().ToArray();
HttpParameterBinding[] binders = Array.ConvertAll(parameters, DetermineBinding);
actionBinding.ParameterBindings = binders;
return actionBinding;
}
private HttpParameterBinding DetermineBinding(HttpParameterDescriptor parameter)
{
HttpConfiguration config = parameter.Configuration;
var attr = new ModelBinderAttribute(); // use default settings
ModelBinderProvider provider = attr.GetModelBinderProvider(config);
IModelBinder binder = provider.GetBinder(config, parameter.ParameterType);
// Alternatively, we could put this ValueProviderFactory in the global config.
var valueProviderFactories = new List<ValueProviderFactory>(attr.GetValueProviderFactories(config)) { new BodyValueProviderFactory() };
return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories);
}
// Derive from ActionBinding so that we have a chance to read the body once and then share that with all the parameters.
private class MvcActionBinding : HttpActionBinding
{
private readonly JsonSerializerSettings serializerSettings;
public MvcActionBinding(JsonSerializerSettings serializerSettings)
{
this.serializerSettings = serializerSettings;
}
// Read the body upfront, add as a ValueProvider
public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
HttpContent content = request.Content;
if (content != null)
{
string result = request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(result))
{
var jsonContent = JObject.Parse(result);
var values = new Dictionary<string, object>();
foreach (HttpParameterDescriptor parameterDescriptor in actionContext.ActionDescriptor.GetParameters())
{
object parameterValue = GetParameterValue(jsonContent, parameterDescriptor);
values.Add(parameterDescriptor.ParameterName, parameterValue);
}
IValueProvider valueProvider = new NameValuePairsValueProvider(values, CultureInfo.InvariantCulture);
request.Properties.Add(Key, valueProvider);
}
}
return base.ExecuteBindingAsync(actionContext, cancellationToken);
}
private object GetParameterValue(JObject jsonContent, HttpParameterDescriptor parameterDescriptor)
{
string propertyValue = jsonContent.Property(parameterDescriptor.ParameterName)?.Value.ToString();
if (IsSimpleParameter(parameterDescriptor))
{
// No deserialization needed for value type, a cast is enough
return Convert.ChangeType(propertyValue, parameterDescriptor.ParameterType);
}
return JsonConvert.DeserializeObject(propertyValue, parameterDescriptor.ParameterType, serializerSettings);
}
private bool IsSimpleParameter(HttpParameterDescriptor parameterDescriptor)
{
return parameterDescriptor.ParameterType.IsValueType || parameterDescriptor.ParameterType == stringType;
}
}
// Get a value provider over the body. This can be shared by all parameters.
// This gets the values computed in MvcActionBinding.
private class BodyValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
actionContext.Request.Properties.TryGetValue(Key, out object vp);
return (IValueProvider)vp; // can be null
}
}
}
To explain, the trick is to first read the request content as a string and then loading it into a JObject.
For each parameter present in actionContext.ActionDescriptor a dictionnary is populated with the parameter name as key and we use the parameter type to add the object value.
Depending on the parameter type we either do a simple cast or use Json.NET to deserialize the value into the desired type.
Please note you may need to add special case for value type to manage for example enumerations or Guid.
In my example, I pass around a JsonSerializerSettings because I have some custom converters that I want to use, be you may not need it.
You should be able to achieve this with the default model binding functionality in Web API 2 itself.
First thing you need to do is pass the data as JSON string as following.
data: JSON.stringify({ "creditLimit": 125000.00 })
The accountId will be read from the URL and the default JsonFormatter of the Web API 2 will try to bind your second parameter request from the body. It will find the creditLimit and will create an instance of RequestObject with the creditLimit populated.
You can then, inside the controller, assign the accountId value to the RequestObject other property. That way you don't need to pass the accountId as part of your request body. You only pass that as part of your URL endpoint.
The following link is a good resource for more in-depth detail.
http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

Deserialize json response

I'm using an API that among other things have these two responses:
{
"enrollDeviceErrorResponse": {
"errorCode": "GRX-1056",
"errorMessage": "DEP Reseller ID missing.
Enter a valid DEP Reseller ID and resubmit your request."
}
}
or
{
"enrollDeviceErrorResponse": [
{
"errorCode": "GRX-1056",
"errorMessage": "DEP Reseller ID missing.
Enter a valid DEP Reseller ID and resubmit your request."
},
{
"errorCode": "DEP-ERR-3003",
"errorMessage": "Order information missing. T
he transaction needs to have one or more valid orders.
Enter valid orders and resubmit your request."
},
{
"errorCode": "DEP-ERR-3001",
"errorMessage": "Transaction ID missing.
Enter a valid transaction ID and resubmit your request."
}
]
}
I've created some classes that can deserialize into these responses:
public class EnrollDeviceErrorRoot
{
public EnrollDeviceErrorRoot()
{
this.EnrollDeviceErrorResponse = new EnrollDeviceErrorResponse();
}
public EnrollDeviceErrorResponse EnrollDeviceErrorResponse { get; set; }
}
public class EnrollDeviceErrorRoot
{
public EnrollDeviceErrorRoot()
{
this.EnrollDeviceErrorResponse = new EnrollDeviceErrorResponse();
}
public EnrollDeviceErrorResponse EnrollDeviceErrorResponse { get; set; }
}
public class EnrollDeviceErrorResponse
{
public string ErrorCode { get; set; }
public string ErrorMessage { get; set; }
}
I am having a hard time coming up with a nice way to determine which one of my classes I should use depending on the response. I currently have this failing code:
var multipleErrors = JsonConvert.DeserializeObject<EnrollDeviceErrorsRoot>(response);
if (multipleErrors.EnrollDeviceErrorResponse != null && multipleErrors.EnrollDeviceErrorResponse.Any())
{
this.StatusCode = multipleErrors.EnrollDeviceErrorResponse.Select(x => x.ErrorCode).Aggregate((a, b) => a + ", " + b);
this.StatusMessage = multipleErrors.EnrollDeviceErrorResponse.Select(x => x.ErrorMessage).Aggregate((a, b) => a + Environment.NewLine + b);
this.HasErrors = true;
return;
}
var singleError = JsonConvert.DeserializeObject<EnrollDeviceErrorRoot>(response);
if (!string.IsNullOrEmpty(singleError.EnrollDeviceErrorResponse.ErrorCode))
{
this.StatusCode = singleError.EnrollDeviceErrorResponse.ErrorCode;
this.StatusMessage = singleError.EnrollDeviceErrorResponse.ErrorMessage;
this.HasErrors = true;
return;
}
Error from code above:
Newtonsoft.Json.JsonSerializationException : Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[Atea.Dep.Core.Service.Models.EnrollDeviceErrorResponse]'
because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer,
not a collection type like an array or List<T>) that can be deserialized from a JSON object.
JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
How are other people handling this deserializations from responses when they don't know what they are getting back from the server? I could of course just look at the raw response and determine that way but are there other cleaner ways to do this? I Can't change the API, I have no idea why there just couldn't be one response with a list with one or more errors.
You can use
JObject.Parse(response)["enrollDeviceErrorResponse"].Type
to determine the type of the response. In 1st case it will be obect, in 2nd - array. Then it will be easy to continue with proper deserialization.
You can use something like this:
var output;
dynamic jObject = JsonConvert.DeserializeObject (outputAPI);
bool isArray = jObj.enrollDeviceErrorResponse.Type == JTokenType.Array;
bool isObject = jObj.enrollDeviceErrorResponse.Type == JTokenType.Object;
if(isObject)
output = JsonConvert.DeserializeObject<EnrollDeviceErrorResponse>(outputAPI);
//else you will use the other class to deserialize the object

RestSharp: Converting results

I get the following JSON that I am trying to convert to a business object using RestSharp
{
"valid":true,
"data":[
{
"dealerId":"4373",
"branchId":"4373",
}
]
}
I wish to convert to:
public class Dealer
{
public string dealerId ;
public string branchId;
}
But this fails, though the JSON is fine:
var client = new RestClient("http://www.????.com.au");
var request = new RestRequest(string.Format("service/autocomplete/dealer/{0}/{1}.json", suburb.PostCode, suburb.City.Trim().Replace(" ", "%20")), Method.GET);
var response2 = client.Execute<Dealer>(request);
return response2.Data;
Your business object doesn't match the response JSON you are getting back. If you want your response to serialize, your C# object would look something like
public class DealerResponse
{
public bool valid { get;set; }
List<Dealer> data { get;set; }
}
public class Dealer
{
public string dealerId;
public string branchId;
}
I haven't tested this code, but even though you are only interested in the information in 'data', your response C# objects still need to represent the whole JSON response to serialize correctly.
Hope that helps.

Categories