2

I have a very simple method in ASP.NET Core 2.2 WebAPI:

[HttpPut]
public async Task<IActionResult> Put([FromForm] SimpleRequest request, [FromForm] string param1)
{
    if (request.ExtraData == null) return BadRequest("Please send data");
    await Task.CompletedTask;
    return Ok("Works");
}

My model looks like this:

public class SimpleRequest
{
    [Required]
    public string UserId { get; set; }
    public RequestType? RequestType { get; set; }
    public DateTime Timestamp { get; set; }
    public bool Test { get; set; }
    public ExtraData ExtraData { get; set; }
}
public enum RequestType
{
    Simple,
    Complex,
}
public class ExtraData
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public ulong Id { get; set; }
    public DateTime RDate { get; set; }
    public uint Nr { get; set; }
}

When I do request from Postman my model is binding correctly: enter image description here

Now I try to create the same using RestSharp. When I use below code it works fine:

var client = new RestClient("https://localhost:44325/api/values");
var request = new RestRequest(Method.PUT);

request.AddParameter("UserId", "Sample", ParameterType.GetOrPost);
request.AddParameter("RequestType", "Simple", ParameterType.GetOrPost);
request.AddParameter("Timestamp", DateTime.Now, ParameterType.GetOrPost);
request.AddParameter("Test", true, ParameterType.GetOrPost);
request.AddParameter("ExtraData.FirstName", "John", ParameterType.GetOrPost);
request.AddParameter("ExtraData.LastName", "Smith", ParameterType.GetOrPost);
request.AddParameter("ExtraData.Id", 1234, ParameterType.GetOrPost);
request.AddParameter("ExtraData.Nr", 1, ParameterType.GetOrPost);
request.AddParameter("ExtraData.RDate", DateTime.Now.AddDays(-2), ParameterType.GetOrPost);

request.AddParameter("param1", "YES", ParameterType.GetOrPost);

var response = client.Execute(request);

But I want to reuse my models because they are shared in separate DLL.

I create my model like so:

var model = new SimpleRequest
{
    UserId = "Sample",
    RequestType = RequestType.Simple,
    Timestamp = DateTime.Now,
    Test = true,
    ExtraData = new ExtraData
    {
        FirstName = "John",
        LastName = "Smith",
        Id = 1234,
        Nr = 1,
        RDate = DateTime.Now.AddDays(-2)
    }
};

but I'm unable to pass this model via request so that it can be correctly deserialized to my object. I've tried using request.AddJsonBody but this fails.

What is the correct way to pass model like above via RestSharp? How should I serialize it (or add as a param) to get this working.

Misiu
  • 4,738
  • 21
  • 94
  • 198
  • You need to also share the code which is having problem. What do you mean by `this fails`? Are you getting any error? Instead of `FromForm` try using `FromBody` – Chetan Jun 14 '19 at 13:38
  • @ChetanRanpariya by fails I mean that I get empty `request` and `param1` in Put method. Problem is I can't change this attribute. Above code it just to illustrate my problem, real code is more complex and already deployed. I need to create a client for already working API. – Misiu Jun 14 '19 at 13:44
  • The API expects the Payload to be sent in `Form` attributes not as request body. So you can not send the payload as a JSON request body. You need to send all they data only via query string. If you want to be able to send the payload as JSON body the API needs to change it's behavior. [This](https://exceptionnotfound.net/asp-net-core-demystified-model-binding-in-mvc/) and [this](https://stackoverflow.com/questions/50453578/asp-net-core-fromform-and-frombody-same-action) are not the answers of your question but they will help you to understand that the API client can not do much here. – Chetan Jun 14 '19 at 13:55
  • @ChetanRanpariya so my question should be how can I "convert" my model to FormData (query string) is this possible (build-in) via RestSharp? – Misiu Jun 14 '19 at 14:06

1 Answers1

2

For FromForm, we need to send request via form-data or x-www-form-urlencoded. You could implement your own way to convert model to x-www-form-urlencoded like:

  1. Extension method to convert

    public static class RestSharpExtension
    {
            public static IDictionary<string, string> ToKeyValue(this object metaToken)
            {
            if (metaToken == null)
            {
                    return null;
            }
    
            JToken token = metaToken as JToken;
            if (token == null)
            {
                    return ToKeyValue(JObject.FromObject(metaToken));
            }
    
            if (token.HasValues)
            {
                    var contentData = new Dictionary<string, string>();
                    foreach (var child in token.Children().ToList())
                    {
                    var childContent = child.ToKeyValue();
                    if (childContent != null)
                    {
                            contentData = contentData.Concat(childContent)
                                                    .ToDictionary(k => k.Key, v => v.Value);
                    }
                    }
    
                    return contentData;
            }
    
            var jValue = token as JValue;
            if (jValue?.Value == null)
            {
                    return null;
            }
    
            var value = jValue?.Type == JTokenType.Date ?
                            jValue?.ToString("o", CultureInfo.InvariantCulture) :
                            jValue?.ToString(CultureInfo.InvariantCulture);
    
            return new Dictionary<string, string> { { token.Path, value } };
            }
    }
    
  2. Use Case

    public async Task<IActionResult> Index()
    {
        var model = new SimpleRequest
                            {
                                UserId = "Sample",
                                RequestType = RequestType.Simple,
                                Timestamp = DateTime.Now,
                                Test = true,
                                ExtraData = new ExtraData
                                {
                                    FirstName = "John",
                                    LastName = "Smith",
                                    Id = 1234,
                                    Nr = 1,
                                    RDate = DateTime.Now.AddDays(-2)
                                }
                            };
        var keyValueContent = model.ToKeyValue();
        keyValueContent.Add("param1", "YES");
        var formUrlEncodedContent = new FormUrlEncodedContent(keyValueContent);
        var urlEncodedString = await formUrlEncodedContent.ReadAsStringAsync();
    
        var client = new RestClient("http://localhost:51420/api/values/Put");
        var request = new RestRequest(Method.PUT);
    
        request.AddParameter("application/x-www-form-urlencoded", urlEncodedString, ParameterType.RequestBody);
    
        var response = client.Execute(request);
    }
    
Edward
  • 28,296
  • 11
  • 76
  • 121
  • Works perfectly. Thank You. This should be built in functionality. I think my use case is valid and not only I want to reuse models. – Misiu Jun 26 '19 at 07:52