I am trying to send a encoded message data(application/x-www-form-urlencoded) to my Restsharp request but i am getting a bad request error message - restsharp

My Request Method: Post
Content-type : application/x-www-form-urlencoded,
using HttpUtility.UrlEncode(RequestBody);
I have an issue here as i am converting my Json Body to UrlEncode for that reason i am sending as a sting to my RestSharp request as shown below.
\\Request Model
public class Request
{
public string MessageId { get; set; }
public bool IsOffline { get; set; }
public string LanguageCode { get; set; }
public string MessageType { get; set; }
}
\\xUnit
[Fact]
public void Login()
{
Request request = new Request
{
MessageId = Guid.NewGuid().ToString(),
MessageType = "KioskLogin",
IsOffline = false,
LanguageCode = "en"
};
String RequestBody = JsonConvert.SerializeObject(request);
//url encode the json
var payload = String.Format("payload={0}", HttpUtility.UrlEncode(RequestBody));
IRestResponse response = _restServices.PostRequest(payload);
Response parsedResponse = JsonConvert.DeserializeObject<Response>(response.Content);
response.StatusCode.Should().Be(HttpStatusCode.OK);
\\I Builded this RestSharp code Like
public IRestResponse PostRequest(String Request)
{
var restClient = new RestClient(ConfigurationReader.Get("Environment:QA:Endpoint"));
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddJsonBody(payload);
IRestResponse response = restClient.Execute(request);
return response;
}
I am able to send the request in URLEncoded form using HttpUtility.UrlEncode(RequestBody) but i have a issue here in my request i am seeing like
"payload=%7B%0A%22MessageId%22%3A%20%222e55dcdf-d877-4970-870e-fef841aff9a1%22%2C%0A%09%22MessageType%22%3A%20%22Login%22%2C%0A%22IsOffline%22%3A%20false%2C%0A%09%22LanguageCode%22%3A%20%22es%22%0A%7D"
But i don't need that to be in " " i need that to be like this
payload=%7B%0A%22MessageId%22%3A%20%222e55dcdf-d877-4970-870e-fef841aff9a1%22%2C%0A%09%22MessageType%22%3A%20%22Login%22%2C%0A%22IsOffline%22%3A%20false%2C%0A%09%22LanguageCode%22%3A%20%22es%22%0A%7D
If it is like this then i will get the response correctly.

I am getting success response when I send my request body in request.AddParameter("Payload", payload, ParameterType.RequestBody); insted of sending in request.AddJsonBody(payload);.

Related

Pass Composite Object to a Post Web API Method

I have the following composite object which I want to pass in web API post method in Xamarin:
public class ShoppingCartCustomizedItems
{
public AddToCart addToCart { get; set; }
public List<AddCustomizedProductSelectionsToCart> AddCustomizedProductSelectionsToCart { get; set; }
}
Below is the API service method to pass the data to the web API:
public static async Task<bool> AddCustomizedItemsInCart(ShoppingCartCustomizedItems addToCart)
{
var httpClient = new HttpClient();
var json = JsonConvert.SerializeObject(addToCart);
var content = new StringContent(json, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", Preferences.Get("accessToken", string.Empty));
var response = await httpClient.PostAsync(AppSettings.ApiUrl + "api/ShoppingCartItems/addCustomizedShoppingCartItem", content);
if (!response.IsSuccessStatusCode) return false;
return true;
}
Finally the web API post method has the following signature:
[HttpPost("[action]")]
public IActionResult addCustomizedShoppingCartItem([FromBody] ShoppingCartCustomizedItem shoppingCartCustomizedItem)
Now whenever I send the post method from Xamarin, the shoppingCartCustomizedItem is always null. How can I address this and what is the best practice to pass composite object in web API method?

Call Web API with multiple parameters

WebAPI
public ActionResult Save([FromBody] string SaveId, List<Product> Products, List<Category> Categories)
{
}
How to call this web API ActionMethod from Controller Action Method?
asp.net Core MVC
public IActionResult SaveConfirmedDocument()
{
var Body = new {
documentID,
Products,
Categories
};
var client = new RestClient(Url);
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + Authorization);
request.AddParameter("application/json", Body, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
}
How to call this web API ActionMethod from Controller Action Method?
Please note that once the request stream is read by an input formatter, it's no longer available to be read again.
For more information, please check the [FromBody] attribute that can not be applied to more than one parameter per action method.
In your action method, you applied [FromBody] attribute to the first parameter, to pass and bind data to List<Product> Products and List<Category> Categories parameters, you can make request with data, like below.
var client = new RestClient(Url);
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + Authorization);
//to make the data can be bound to action parameters well
//please generate query string data based on your actual model class and properties, like below
request.AddParameter("Products[0].Id", 1, ParameterType.QueryString);
request.AddParameter("Products[0].Name", "PName1", ParameterType.QueryString);
request.AddParameter("Products[1].Id", 2, ParameterType.QueryString);
request.AddParameter("Products[1].Name", "PName2", ParameterType.QueryString);
request.AddParameter("Categories[0].Id", 1, ParameterType.QueryString);
request.AddParameter("Categories[0].Name", "CategoryName1", ParameterType.QueryString);
request.AddParameter("Categories[1].Id", 2, ParameterType.QueryString);
request.AddParameter("Categories[1].Name", "CategoryName2", ParameterType.QueryString);
request.AddJsonBody(Body);
IRestResponse response = client.Execute(request);
Test Result
Besides, if you'd like to pass both of these data through request body, you can try to modify the code like below.
Action method
public ActionResult Save([FromBody] ProductCategoryViewModel productsAndCategories)
{
//...
Model class
public class ProductCategoryViewModel
{
public string SaveId { get; set; }
public List<Product> Products { get; set; }
public List<Category> Categories { get; set; }
}
Code of making request(s) with test data
var Body = new {
SaveId = "123",
Products = new List<Product> {
new Product {
Id = 1,
Name = "PName1"
},
new Product {
Id = 2,
Name = "PName2"
},
new Product {
Id = 3,
Name = "PName3"
}
},
Categories = new List<Category>
{
new Category
{
Id = 1,
Name = "CategoryName1"
}
}
};
var client = new RestClient(Url);
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + Authorization);
request.AddJsonBody(Body);
IRestResponse response = client.Execute(request);
Just create new class like this:
public class MyViewModel
{
public string SaveId {get; set;}
public List<Product> Products {get; set;}
public List<Category> Categories {get; set;}
}
And change your action to this:
public ActionResult Save(MyViewModel viewModel)
{
}
Just select what do you prefer - DocumentId or SaveId. It should be the same in the both places.

400 Bad Request when trying to send api request with IFormFile in request object

I am having a hard time figuring out how to send an IFormFile object part of the request. It is an API call to upload an image. I have found a few resources and have tried each suggestion but I always get a 400 Bad Request response when I try and hit the API. Both the API and client are ASP.NET Core 2.1
Call to the API
public async Task<ApiResponse<ImageDto>> AddImageToWebsite(AddImageToWebsiteRequest request)
{
try
{
HttpClient client = new HttpClient();
var url = $"{_apiInfo.Url}/portal/AddImageToWebsite";
byte[] data;
using (var br = new BinaryReader(request.Image.OpenReadStream()))
{
data = br.ReadBytes((int) request.Image.OpenReadStream().Length);
}
var bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent();
multiContent.Add(bytes, "file", request.Image.FileName);
multiContent.Add(new StringContent(request.WebsiteId.ToString()), "WebsiteId");
multiContent.Add(new StringContent(request.AltText), "AltText");
// BREAKS AFTER THIS POST CALL
var apiResponse = await client.PostAsync(url, multiContent);
// DESERIALIZE RESPONSE TO RESPONSE OBJECT HERE
}
catch (Exception ex)
{
Log.Error(ex, "Error calling api");
return ApiResponse.InternalError<ImageDto>(ex.Message);
}
}
AddImageToWebsiteRequest
public class AddImageToWebsiteRequest
{
public int WebsiteId { get; set; }
public IFormFile Image { get; set; }
public string AltText { get; set; }
}
API CALL
[HttpPost]
[Route("AddImageToWebsite")]
public async Task<JsonResult> AddImageToWebsite(AddImageToWebsiteRequest request)
{
return await this.HandleRequest(async () =>
{
var website = _dataAccess.GetWebsite(request.WebsiteId);
if (website == default(Website))
{
return ApiResponse.NotFound<ImageDto>("Website not found");
}
// UPLOAD IMAGE CODE HERE
}
}
It does not even hit the API call. I also tried posting it as follows, and it worked as long as I did not have an image in the serialized object.
Another Attempt
var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var apiResponse = await client.PostAsync(url, stringContent);
// DESERIALIZE RESPONSE TO RESPONSE OBJECT HERE
I have tried so many different recommendations online and none seem to work.
IFormFile is only for multipart/form-data encoded POST requests, i.e. a traditional form post. If you're sending JSON, your "upload" needs to be a Base64 string and you need to bind to a byte[]:
public class AddImageToWebsiteRequest
{
public int WebsiteId { get; set; }
public byte[] Image { get; set; }
public string AltText { get; set; }
}
JsonConvert.SerializeObject will automatically convert byte[]s into Base64 strings.
How are you sending this from the view? If you are using a form, you can just give it the multipart/form-data type, give the input type of file and then bind it to the IFormFile in the parameter.
View:
<form id="fileupload" action="yourpath/AddImageToWebsite/" method="POST" enctype="multipart/form-data">
<button type="submit" class="btn btn-primary start">
</button>
<input type="file" name="YourFile"/>
<!--Whatever other things you need to input, use hidden fields-->
</form>
Controller:
[HttpPost]
[Route("AddImageToWebsite")]
public async Task<JsonResult> AddImageToWebsite(IFormFile YourFile)
{
//Do what you need....
}

PostAsJsonAsync And Anonymous Types - 404 Not Found Errors

I've been unsuccessfully trying to get this working.
I'm using AttributeRouting on the API and I have this method defined on my WebAPI:
[POST("update"), JsonExceptionFilter]
public HttpResponseMessage PostUpdate([FromJson] long id, DateTime oriDt, string notes, int score)
When I try to call this with the following code:
using (var httpClient = new HttpClient(CreateAuthorizingHandler(AuthorizationState)))
{
var args = new { id, oriDt, notes, score };
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("id", id.ToString()));
postData.Add(new KeyValuePair<string, string>("oriDt", oriDt.ToString(_dateService.DefaultDateFormatStringWithTime)));
postData.Add(new KeyValuePair<string, string>("notes ", notes));
postData.Add(new KeyValuePair<string, string>("score ", score.ToString(CultureInfo.InvariantCulture)));
var response = httpClient.PostAsJsonAsync(ApiRootUrl + "update", postData).Result;
if (response.IsSuccessStatusCode)
{
var data = response.Content.ReadAsAsync<bool>().Result;
return data;
}
return null;
}
The response is always 404 - not found. What am I missing here? I've tried using an anonymous object called args in the code with the same issue.
I've also tried it with and witout the [FromJson] attribute as well with the same results.
First, remove [FromJson]. With that, you have this action method.
public HttpResponseMessage PostUpdate(long id, DateTime oriDt,
string notes, int score)
If you POST to the URI below, it will work.
/update/123?oridt=somedate&notes=somenote&score=89
If you want to use request body to POST the fields (as you are doing with the client code), declare a class containing properties with name same as the field in request body.
public class MyDto
{
public long Id { get; set; }
public DateTime OriDt { get; set; }
public string Notes { get; set; }
public int Score { get; set; }
}
Then change the action method like this.
public HttpResponseMessage PostUpdate(MyDto dto)

MVC Web Api returning serialized response instead of css

I am having an issue returning css from a web api controller. The code takes a request for a css file and returns it after reading it from the database.
The problem is that the web api code seems to be serializing the response and returning that instead of the css itself.
Here you can see a link tag that the browser is sending to the server which should return css. You can also see that the response looks like a serialization of my css instead of just the css string.
My request and response headers:
My controller looks like this:
public HttpResponseMessage Get(string fileName, string siteId, int id)
{
var fileData = ReadSomeCssFromTheDatabase();
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(fileData);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/css");
result.Headers.CacheControl = new CacheControlHeaderValue();
result.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
result.Headers.CacheControl.MustRevalidate = true;
return result;
}
There is a “text/css” formatter installed that is being created but not being hit for some reason.
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var taskCompletionSource = new TaskCompletionSource<object>();
try
{
var memoryStream = new MemoryStream();
readStream.CopyTo(memoryStream);
var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
taskCompletionSource.SetResult(s);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override bool CanWriteType(Type type)
{
return false;
}
}
What am I doing wrong?
Your formatter would not be hit because you are not going through content negotiation process (as you are returning HttpResponseMessage in your action...you could use Request.CreateResponse<> to make conneg process run)
You are trying to 'write' the css content right?...but i see that CanWriteType is returning 'false' and also you seem to be overriding ReadFromStreamAsync instead of WriteToStreamAsync?
An example of how you could do(from what i understood about the above scenario):
public class DownloadFileInfo
{
public string FileName { get; set; }
public string SiteId { get; set; }
public int Id { get; set; }
}
public HttpResponseMessage Get([FromUri]DownloadFileInfo info)
{
// validate the input
//Request.CreateResponse<> would run content negotiation and get the appropriate formatter
//if you are asking for text/css in Accept header OR if your uri ends with .css extension, you should see your css formatter getting picked up.
HttpResponseMessage response = Request.CreateResponse<DownloadFileInfo>(HttpStatusCode.OK, info);
response.Headers.CacheControl = new CacheControlHeaderValue();
response.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
response.Headers.CacheControl.MustRevalidate = true;
return response;
}
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return type == typeof(DownloadFileInfo);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
//use the 'value' having DownloadFileInfo object to get the details from the database.
// Fead from database and if you can get it as a Stream, then you just need to copy it to the 'writeStream'
}
}