I have a problem with post request with RestSharp. I have 2 classes:
public class UnitToPost
{
public bool floating_point { get; set; }
public Dictionary<string, TranslationUnitToPost> translations { get; set; }
}
public class TranslationUnitToPost
{
public string name { get; set; }
}
And I want to send it with post request:
client = new RestClient(adresApi);
client.AddDefaultHeader("Authorization", "Bearer " + key);
IRestRequest updateProduct = new RestRequest("units", Method.POST);
ShoperModel.UnitToPost unitToPost = new ShoperModel.UnitToPost();
unitToPost.floating_point = true;
ShoperModel.TranslationUnitToPost transUnit = new ShoperModel.TranslationUnitToPost();
transUnit.name = "namename";
unitToPost.translations = new Dictionary<string, ShoperModel.TranslationUnitToPost>();
unitToPost.translations.Add("pl_PL", transUnit);
updateProduct.RequestFormat = RestSharp.DataFormat.Json;
updateProduct.AddBody(unitToPost);
IRestResponse updateProductResponse = this.client.Execute(updateProduct);
And I always get an error:
[RestSharp.RestResponse] = "StatusCode: InternalServerError,
Content-Type: application/json, Content-Length: -1)"
Content =
"{\"error\":\"server_error\",\"error_description\":\"Operation
Failed\"}"
What is the cause of it? Could it be because of Dictionary in my class?
I've run your code and it issues a request with a valid JSON body.
POST http://..../units HTTP/1.1
Accept: application/json,application/xml, text/json, text/x-json, text/javascript, text/xml
Authorization: Bearer a
User-Agent: RestSharp/105.2.3.0
Content-Type: application/json
Host: .....
Content-Length: 84
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
{"floating_point":true,"translations":[{"Key":"pl_PL","Value":{"name":"namename"}}]}
It looks like the problem may be with the receiving server. If you're not doing so already I'd suggest running Fiddler (http://www.telerik.com/fiddler) and inspecting the request/response.
Edit...
I only just realised you want the JSON body to be :-
{"floating_point":true,"translations":{"pl_PL":{"name":"namename"}}}
I did find a RestSharp issue that covers this :-
https://github.com/restsharp/RestSharp/issues/696
This includes a post where someone has used an ExpandoObject to get the required result.
http://theburningmonk.com/2011/05/idictionarystring-object-to-expandoobject-extension-method/
However, I found it easier to use JSON .NET to serialise and set the body with the following code:-
updateProduct.AddBody(JsonConvert.SerializeObject(unitToPost));
Related
I'm trying to set up a file upload request in a ServiceStack TypeScript client that also includes the month for which the file is relevant. How do I set up the request so that both come through to the server?
I've tried various changes, including manually changing headers to try to force Content-Type to be application/json, which didn't work (but I suspect would break the file upload even if it did).
Client-side API:
export const serviceApi = {
importData: (month: string, file: File) => {
var client = new JsonServiceClient("");
var request = new DTOs.ImportData();
// At this point, the month has a value
request.month = month.replace('/', '-').trim();
let formData = new FormData();
formData.append('description', file.name);
formData.append('type', 'file');
formData.append('file', file);
const promise = client.postBody(request, formData);
return from(promise);
},
};
DTO definition:
[Route("/api/data/import/{Month}", "POST")]
public class ImportData : IReturn<ImportDataResponse>
{
public string Month { get; set; }
}
public class ImportDataResponse : IHasResponseStatus
{
public ResponseStatus ResponseStatus { get; set; }
}
Server-side API:
[Authenticate]
public object Post(ImportData request)
{
if (Request.Files == null || Request.Files.Length <= 0)
{
throw new Exception("No import file was received by the server");
}
// This is always coming through as null
if (request.Month == null)
{
throw new Exception("No month was received by the server");
}
var file = (HttpFile)Request.Files[0];
var month = request.Month.Replace('-', '/');
ImportData(month, file);
return new ImportDataResponse();
}
I can see that the file is coming through correctly on the server side, and I can see an HTTP request going through with the month set in the query string parameters as "07-2019", but when I break in the server-side API function, the month property of the request is null.
Update, here are the HTTP Request/Response headers:
Request Headers
POST /json/reply/ImportData?month=07-2019 HTTP/1.1
Host: localhost:40016
Connection: keep-alive
Content-Length: 7366169
Origin: http://localhost:40016
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryI8CWlbw4tP80PkpZ
Accept: */*
Referer: http://localhost:40016/data
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: _ga=GA1.1.673673009.1532913806; ASP.NET_SessionId=gtwdk3wsvdn0yulhxyblod3g; __utmc=111872281; __utmz=111872281.1533684260.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); ss-opt=perm; __utma=111872281.673673009.1532913806.1550789161.1550794391.20; _gid=GA1.1.893581387.1558389301; ss-id=kfq4G0GYb3WldSdCaRyJ; ss-pid=aZ400sqM4n3TQgNVnHS2
Response Headers
HTTP/1.1 500 Exception
Cache-Control: private
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/10.0
X-Powered-By: ServiceStack/5.10 NET45 Win32NT/.NET
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RTpcVEZTXFNvdXJjZVxNZWRpc2VuXFdlYnNpdGVzXE9OaWlDU1xNYWluXFNvdXJjZVxPbmlpY3NSZWFjdC1QYXltZW50c1xPbmlpY3NSZWFjdFxPbmlpY3NSZWFjdFxqc29uXHJlcGx5XEltcG9ydE1CU0NvZGVz?=
X-Powered-By: ASP.NET
Date: Tue, 21 May 2019 21:49:03 GMT
Content-Length: 605
Query String Parameters
month=07-2019
You'll be able to upload a file using JavaScript's fetch API directly, e.g:
let formData = new FormData();
formData.append('description', file.name);
formData.append('type', 'file');
formData.append('file', file);
fetch('/api/data/import/07-2019', {
method: 'POST',
body: formData
});
Otherwise if you want to use ServiceStack's TypeScript JsonServiceClient you would need to use the API that lets you post the Request DTO with a separate request body, e.g:
formData.append('month', '07-2019');
client.postBody(new ImportData(), formData);
I don't think the month should be part of the request header, that's kinda unorthodox. It should be part of the form data.
If you did:
formData.append('Month', month.replace('/', '-').trim());
client side, then request.Month or request.content.Month should work, depending on how the request object is handled in your instance.
I'm looking to send complex objects to my .NET Core API without FromBody tags.
What I am looking to do is simple with JQuery, but I can't figure out for the life of me how to duplicate the logic from a C# Web API Client.
For the sake of testing, I have a fairly simple object.
[Serializable]
public class FilterModel
{
public int? PageSize { get; set; }
public int Page { get; set; }
}
For the sake of testing, we also have an extremely simple controller method
[HttpPost("TEST")]
public virtual int GetTEST(Common.FilterModel filter, Common.FilterModel filter2)
{
return ((filter.Page * filter.PageSize ?? 0) + (filter2.Page * filter2.PageSize ?? 0));
}
Obviously the use case for the code would me more complex, but I am just trying to get the values at this time.
The JQuery to call this method is fairly straight forward and works flawlessly:
var myData = {"filter":{"pageSize":3,"page":2},
"filter2":{"pageSize":19,"page":1}};
$.ajax({
type: 'POST',
url: '/api/Authentication/TEST',
data: myData
}).done(function (data, statusText, xhdr) {
JsonResponse = data;
console.log(data);
}).fail(function (xhdr, statusText, errorText) {
//console.log(JSON.stringify(xhdr));
});
But trying to duplicate that logic from an HttpClient results in 0; the model is always empty when received on the API side. I've tried a few different methods, from JSON Serializing an ArrayList/Dictionary to what I'm adding below, but I'm hitting a dead end unsure where I went wrong.
HttpClient client = new HttpClient
{
BaseAddress = new Uri("http://localhost:65447/")
};
ApiCommon.FilterModel filter = new ApiCommon.FilterModel()
{
PageSize = 10,
Page = 1
};
ApiCommon.FilterModel filter2 = new ApiCommon.FilterModel()
{
PageSize = 9,
Page = 41
};
MultipartFormDataContent mpContent = new MultipartFormDataContent();
StringContent content = new StringContent(JsonConvert.SerializeObject(filter));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
mpContent.Add(content, "filter");
content = new StringContent(JsonConvert.SerializeObject(filter2));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
mpContent.Add(content, "filter2");
var retval =
JsonConvert.DeserializeObject<int>(
client.PostAsync("/api/Authentication/TEST", mpContent)
.Result.Content.ReadAsStringAsync().Result);
Console.WriteLine(retval);
Is there any way to imitate the JQuery call to work through an HttpClient? Am I missing something critical here?
EDIT:
After getting fiddler to pick it up, this is the raw data for each, I am going to try to match this with my changes.
JQuery fiddler
ePOST http://localhost:65447/api/Authentication/TEST HTTP/1.1
Host: localhost:65447
Connection: keep-alive
Content-Length: 86
Accept: */*
Origin: http://localhost:65447
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:65447/TestHarness
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
filter%5BpageSize%5D=3&filter%5Bpage%5D=2&filter2%5BpageSize%5D=19&filter2%5Bpage%5D=1
C# fiddler
POST http://local.dev:65447/api/Authentication/TEST HTTP/1.1
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Host: local.dev:65447
156
{"filter":{"<IncludeFullData>k__BackingField":false,"<SortBy>k__BackingField":"","<PageSize>k__BackingField":10,"<Page>k__BackingField":1,"<Operation>k__BackingField":""},"filter2":{"<IncludeFullData>k__BackingField":false,"<SortBy>k__BackingField":"","<PageSize>k__BackingField":9,"<Page>k__BackingField":41,"<Operation>k__BackingField":""}}
0
fiddler settings:
Url
http://localhost:8080/lzp/servRoot (post)
Headers
User-Agent: Fiddler
Host: localhost:8080
Content-Length: 86
Content-Type: application/x-www-form-urlencoded
RequestBody
className=com.lzp.service.UserInfoService&methodName=login&user_name=123&user_pwd=123
And response
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Content-Type: text/json;charset=gbk
Transfer-Encoding: chunked
{"msg":false}
It returns {"msg":false} using fiddler.
Here is my code using RestSharp.
RestClient client = new RestClient("http://localhost:8080");
RestRequest request = new RestRequest("lzp/servRoot", Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("className", "com.lzp.service.UserInfoService");
request.AddParameter("methodName", "login");
request.AddParameter("user_name", "123");
request.AddParameter("user_pwd", "123");
var response = client.Execute<TestResultModel>(request);
var res = response.Data;
and the TestResultModel:
public class TestResultModel
{
public string msg { get; set; }
}
Why can't I get deserialize response data?
I am creating a restsharp request in order to trigger a batch direct send push request off to Azure notification hub.
I am receiving a 400 Bad Request response, with the message; Could not find 'notifications' part in the multipart content supplied.
The request looks like such;
const string multipartContentType = "multipart/form-data; boundary=\"simple-boundary\"";
const string authSignature = "myvalidauthsignature";
const string url = "mynotificanhuburl";
const string message = "Some message";
var restClient = new RestClient
{
BaseUrl = new Uri(url),
Proxy = new WebProxy("127.0.0.1", 8888),
};
var request = new RestSharp.RestRequest(Method.POST)
{
RequestFormat = DataFormat.Json,
AlwaysMultipartFormData = true
};
request.AddHeader("Content-Type", multipartContentType);
request.AddHeader("Authorization", authSignature);
request.AddHeader("ServiceBusNotification-Format", "gcm");
request.AddParameter("notification", JsonConvert.SerializeObject(new { data = new { message } }), ParameterType.GetOrPost);
request.AddParameter("devices", JsonConvert.SerializeObject(new List<string> { "123", "456" }), ParameterType.GetOrPost);
var response = restClient.Execute(request);
I can see the raw request via Fiddler;
POST https://xxxxx.servicebus.windows.net/xxx/messages/$batch?direct&api-version=2015-04 HTTP/1.1
Authorization: [redacted]
ServiceBusNotification-Format: gcm
Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml
User-Agent: RestSharp/105.2.3.0
Content-Type: multipart/form-data; boundary=-----------------------------28947758029299
Host: [redacted]
Content-Length: 412
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
-------------------------------28947758029299
Content-Disposition: form-data; name="notification"
{"data":{"message":"Some message"}}
-------------------------------28947758029299
Content-Disposition: form-data; name="devices"
["123","456"]
-------------------------------28947758029299--
Which looks about right. If I copy this into postman with the headers etc, I can see the same error response. HOWEVER in postman when I remove the quote marks around the parameter names, it works and returns a 201 Created response.
So this works....
Content-Disposition: form-data; name=notification
This doesn't
Content-Disposition: form-data; name="notification"
Which seems really peculiar. As we are using restsharp however I don't think I have any direct control over the raw output for the request body. I am wondering;
Is there a restsharp setting to manage these quote, perhaps a formatting setting
Why would the Azure endpoint reject a parameter name with quotes
It is possible that the issue is elsewhere and this is a red herring, but this does seem to be responsible.
Appreciate any help...
According our documentation, request should look like this:
POST https://{Namespace}.servicebus.windows.net/{Notification Hub}/messages/$batch?direct&api-version=2015-08 HTTP/1.1
Content-Type: multipart/mixed; boundary="simple-boundary"
Authorization: SharedAccessSignature sr=https%3a%2f%2f{Namespace}.servicebus.windows.net%2f{Notification Hub}%2fmessages%2f%24batch%3fdirect%26api-version%3d2015-08&sig={Signature}&skn=DefaultFullSharedAccessSignature
ServiceBusNotification-Format: gcm
Host: {Namespace}.servicebus.windows.net
Content-Length: 431
Expect: 100-continue
Connection: Keep-Alive
--simple-boundary
Content-Type: application/json
Content-Disposition: inline; name=notification
{"data":{"message":"Hello via Direct Batch Send!!!"}}
--simple-boundary
Content-Type: application/json
Content-Disposition: inline; name=devices
["Device Token1","Device Token2","Device Token3"]
--simple-boundary--
So, the name parameter's value is not quoted (name=devices). I've not found any RFC which would explicitly specify requirements regarding the situation. However, in examples inside of RFCs a values appear quoted. And because of that I'm going to fix the service to support both options. Fix should come with next deployment in a week or so.
I was plagued by this for a few days and was diligently searching for a solution with RestSharp and was unable to find one as it always default the content type to "multipart/form-data". I know the OP was looking for a way to do this with RestSharp but I don't believe there is currently.My solution comes from a few different posts over a few days so I apologize for not linking to them. Below is a sample Function to perform a multipart/related POST with json body and base64 pdf string as the file.
public static void PostBase64PdfHttpClient(string recordID, string docName, string pdfB64)
{
string url = $"baseURL";
HttpClient client = new HttpClient();
var myBoundary = "------------ThIs_Is_tHe_bouNdaRY_";
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"UN:PW"));
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{url}/api-endpoint");
request.Headers.Date = DateTime.UtcNow;
request.Headers.Add("Accept", "application/json; charset=utf-8");
MultipartContent mpContent = new MultipartContent("related", myBoundary);
mpContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/related; boundary={myBoundary}");
dynamic jObj = new Newtonsoft.Json.Linq.JObject(); jObj.ID = recordID; jObj.Name = docName;
var jsonSerializeSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var json = JsonConvert.SerializeObject(jObj, jsonSerializeSettings);
mpContent.Add(new StringContent(json, Encoding.UTF8, "application/json"));
mpContent.Add(new StringContent(pdfB64, Encoding.UTF8, "application/pdf"));
request.Content = mpContent;
HttpResponseMessage response = client.SendAsync(request).Result;
}
I have an AJAX form which post a form-data to a local API url: /api/document. It contains a file and a custom Id. We simply want to take the exact received Request and forward it to a remote API at example.com:8000/document/upload.
Is there a simple way of achieve this "forward" (or proxy?) of the Request to a remote API using Asp.NET Core?
Below we had the idea to simply use Web API Http client to get the request and then resend it (by doing so we want to be able to for example append a private api key from the backend), but it seems not to work properly, the PostAsync doesn't accept the Request.
Raw request sent by Ajax
POST http://localhost:62640/api/document HTTP/1.1
Host: localhost:62640
Connection: keep-alive
Content-Length: 77424
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn1BS5IFplQcUklyt
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fr;q=0.6
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="fileToUpload"; filename="test-document.pdf"
Content-Type: application/pdf
...
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="id"
someid
------WebKitFormBoundaryn1BS5IFplQcUklyt--
Backend Code
Our .NET Core backend has a simple "forward to another API" purpose.
public class DocumentUploadResult
{
public int errorCode;
public string docId;
}
[Route("api/[controller]")]
public class DocumentController : Controller
{
// POST api/document
[HttpPost]
public async Task<DocumentUploadResult> Post()
{
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
HttpResponseMessage response = await client.PostAsync("/document/upload", Request.Form);
if (response.IsSuccessStatusCode)
{
retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
}
return retValue;
}
}
We have a GET request (not reproduced here) which works just fine. As it doesn't have to fetch data from locally POSTed data.
My question
How to simply pass the incoming local HttpPost request and forwarding it to the remote API?
I searched A LOT on stackoverflow or on the web but all are old resources talking about forwarding Request.Content to the remote.
But on Asp.NET Core 1.0, we don't have access to Content. We only are able to retrieve Request.Form (nor Request.Body) which is then not accepted as an argument of PostAsync method:
Cannot convert from Microsoft.AspNetCore.Http.IformCollection to
System.Net.Http.HttpContent
I had the idea to directly pass the request to the postAsync:
Cannot convert from Microsoft.AspNetCore.Http.HttpRequest to
System.Net.Http.HttpContent
I don't know how to rebuild expected HttpContent from the local request I receive.
Expected response
For information, When we post a valid form-data with the custom Id and the uploaded file, the remote (example.com) API response is:
{
"errorCode": 0
"docId": "585846a1afe8ad12e46a4e60"
}
Ok first create a view model to hold form information. Since file upload is involved, include IFormFile in the model.
public class FormData {
public int id { get; set; }
public IFormFile fileToUpload { get; set; }
}
The model binder should pick up the types and populate the model with the incoming data.
Update controller action to accept the model and proxy the data forward by copying content to new request.
[Route("api/[controller]")]
public class DocumentController : Controller {
// POST api/document
[HttpPost]
public async Task<IActionResult> Post(FormData formData) {
if(formData != null && ModelState.IsValid) {
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
var multiContent = new MultipartFormDataContent();
var file = formData.fileToUpload;
if(file != null) {
var fileStreamContent = new StreamContent(file.OpenReadStream());
multiContent.Add(fileStreamContent, "fileToUpload", file.FileName);
}
multiContent.Add(new StringContent(formData.id.ToString()), "id");
var response = await client.PostAsync("/document/upload", multiContent);
if (response.IsSuccessStatusCode) {
var retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
return Ok(reyValue);
}
}
//if we get this far something Failed.
return BadRequest();
}
}
You can include the necessary exception handlers as needed but this is a minimal example of how to pass the form data forward.