Calling HttpPost with a single string parameter not working - c#

I have the following .Net Core 2.2 controller method, which is always receiving a null value:
public class ValuesController : Controller
{
[HttpPost]
public async Task<IActionResult> AddValue(string value)
{
var newValue = await DoSomethingWithValue(value); // value is null here
// Other code
return Ok();
}
}
The calling code is as follows:
string newValue = Guid.NewGuid();
//var stringContent = new StringContent(newValue);
var result = await httpClient.PostAsync(uri + newValue, null);
if (!result.IsSuccessStatusCode)
The controller method is called successfully, but whether I try to pass the value as HttpContent, or as a query parameter (e.g. myuri/AddValue/123) it still comes through as null. What could I be doing wrong here?

First, that's not a query param; that's a route segment. If you want to receive it that way, you need to specify a route param:
[HttpPost("AddValue/{value}")]
Otherwise, you need to send it as an actual query param, i.e. myuri/AddValue?value=123.
As for the post, the default binding is FromForm, which is expecting an x-www-form-urlencoded or multipart/form-data encoded body, which is not what you're sending. You would need to do:
var stringContent = new StringContent($"value={newValue}", Encoding.UTF8, "application/x-www-form-urlencoded");
Or you can actually use FormUrlEncodedContent:
var values = new Dictionary<string, string>
{
["value"] = Guid.NewGuid().ToString()
};
var formUrlEncodedContent = new FormUrlEncodedContent(values);

Related

Can not get request body from HttpContext in IAsyncExceptionFilter

In my .NET Core 3.1 Web API project, I have a filter that implements IAsyncExceptionFilter. In that filter, I'm catching unhandled exceptions, logging them to database along with the incoming HTTP request data(at least I intend to).
My filter looks like this:
public class ServiceExceptionInterceptor : IAsyncExceptionFilter
{
public async Task OnExceptionAsync(ExceptionContext context)
{
var errorLogService = (IErrorLogService) context.HttpContext.RequestServices.GetService(typeof(IErrorLogService));
var req = context.HttpContext.Request;
var method = req.Method;
var uri = $"{req.Scheme}://{req.Host}/{req.Path}{req.QueryString}";
string body = null;
if (req.ContentLength != null)
{
var reader = new StreamReader(req.Body);
// body is empty string
body = await reader.ReadToEndAsync();
}
var exception = new Models.DB.Exception()
{
Type = context.Exception.GetType().ToString(),
Message = context.Exception.Message,
StackTrace = context.Exception.StackTrace,
CreatedAt = DateTime.UtcNow,
Method = method,
Uri = uri,
Body = body
};
errorLogService.LogError(exception);
var error = new Error()
{
StatusCode = 500,
Message = "Something went wrong. There were some technical issues while processing your request."
};
context.Result = new JsonResult(error) { StatusCode = StatusCodes.Status500InternalServerError };
}
In the code above, I check if there is any request body with req.ContentLength != null, and it seems like it works fine. When I get a request with no body, req.ContentLength is null, and if the request does have a body, it has a value.
But when it comes to getting the content of the request body when there is one, the value I get is always empty string.
As far as I understood from some articles and forum posts, the reason behind this is that the body is read in my controller endpoint, and can not be re-read. I saw a solution that uses the EnableBuffering() method in a middleware in Startup like below, but it did not work for me.
app.Use(async (context, next) => {
context.Request.EnableBuffering();
await next();
});
When I remove the [FromBody] part from my controller, only then I am able to get the request body.
[HttpPost]
[Route("api/questions/{questionId}/comments")]
public async Task<ActionResult<Comment>> AddComment(int questionId, /*[FromBody] AddCommentRequestBody requestBody*/) // -> removing [FromBody] allows the filter to be able to read the request body
{
throw new Exception("test exception");
// ...
// AddComment implementation...
// ...
return Ok(addedComment);
}
Is there any way to read the request body without removing [FromBody] from my controller endpoints?

Disable ModelBinder in .NetCore

HeyGuys
I'm working on a WebApi project that receives requests from clients and redirects these requests to other services that are not open for direct access.
By default, .Net serializes and deserializes the Json request parameters automatically, so I need to re-serialize them before calling the appropriate service. The same problem occurs when receiving the service response. I need to deserialize it before sending the response to the user; otherwise .Net framework will serialize it one more time, resulting in a "Json of Json" response.
I found this answer but it does not seem to work with .NetCore; so I tried to create my own ModelBinder that just reads the Json object and returns it.
class JsonUnformatterBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
return new JsonUnformatterBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
}
and
class JsonUnformatterBinder : IModelBinder
{
private readonly IModelBinder _fallbackBinder;
public JsonUnformatterBinder(IModelBinder fallbackBinder)
{
_fallbackBinder = fallbackBinder;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string currMethod = bindingContext.ActionContext.HttpContext.Request.Method;
if ("POST".Equals(currMethod) || "PUT".Equals(currMethod))
{
string strData = new StreamReader(bindingContext.ActionContext.HttpContext.Request.Body).ReadToEnd();
bindingContext.Result = ModelBindingResult.Success(strData);
return Task.CompletedTask;
}
return _fallbackBinder.BindModelAsync(bindingContext);
}
}
This code is very simple, it was my first attempt and it worked well for my purposes. However, I still get the "Json of Json" problem when I take the second service answer and returns back to the user.
I basically have no idea what I can do to overcome this, so any workaround is welcome here.
If you need just redirect a request without modification, you could read it from input stream directly and send it to inner service. You could also use such approach to read responce from inner service.
//1. Set empty parameter list in action then neither serializator nor model binder are not invoked.
public async Task<ContentResult> ProxyAction(/*empty parameter list*/)
{
var newUrl = #"https://stackoverflow.com";
var data = this.Request.Body;
using (var client = new HttpClient())
{
//2. Read request body from input stream.
var reader = new StreamReader(data);
var json = reader.ReadToEnd();
using (var content = new StringContent(json))
{
//3. Set correct content type
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(this.Request.ContentType);
//4. Post request to inner service
var response = await client.PostAsync(newUrl, content);
//5. Read response without deserialization
var innerResponse = await response.Content.ReadAsStringAsync();
var contentType = response.Content.Headers.ContentType.ToString();
var statusCode = response.StatusCode;
//6. Return inner response without serialization
var outerResponse = this.Content(innerResponse, contentType);
outerResponse.StatusCode = (int)statusCode;
return outerResponse;
}
}
}

C# how to not serialize an already serialized string with Content

So I have built an REST API that in itself also consumes another API. Now I could just call the other api, create objects from that call and then make a new request and send it on it's way but that would use up a bit of performance.
I tried just sending the second request again but the problem is that Content serializes it again so I get alot of backslashes. This is my code that does this:
[Route("")]
public IHttpActionResult GetAllDevices()
{
var request = new RestRequest();
request = new RestRequest("devices", Method.GET);
IRestResponse response = client.Execute(request);
return Content(HttpStatusCode.OK, response.Content);//response.Content get's serialized again.
}
As I said, I could deserialized the first call and then just put that in Content, but it feels unnecessary.
Here is one way of doing it, remember to set the content-type explicitly to application/json if needed:
[HttpGet]
[Route("test")]
public HttpResponseMessage Test()
{
const string json = "{ \"test\": 123 }"; // from RestClient
var res = Request.CreateResponse();
res.Content = new StringContent(json);
res.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
return res;
}

Asp.net WebApi POST request does not work

I have a web api method which is as follows,
[Route("api/Nltk")]
[HttpPost]
public string Create([FromBody]string text)
{
string result = "Error";
//Assign result here
return result;
}
When I make a POST request I get 404 - File or directory not found. error. While other methods (which are all GET methods) in the same api work just fine. For further detail http://ozgurakpinar.net/api/Nltk is the complete url.
The following is one of the methods I've tried, so far.
var values = new Dictionary<string, string> {
{ "text", "This is a relatively short sentence." },
};
HttpClient client = new HttpClient();
var content = new FormUrlEncodedContent(values);
var result = client.PostAsync("http://ozgurakpinar.net/api/Nltk", content).Result;
Edit: After I added the FromBody attribute the method is finally called, but the value of text is null.
First, I think you may have a typo. It should be [FromBody] not [FormBody].
Secondly, you need to append an "=" before your content string.
ie:
client.PostAsync("http://ozgurakpinar.net/api/Nltk", "=" + content)
When you are giving a name to your value then actually you are looking for a class with that member. In your case you are posting to a method which accept a class having a text member of string type.
If you need to post to a method having a string parameter then no need to give it a name. Below is working code.
var values = new Dictionary<string, string> {{ "", "This is a relatively short sentence." }};
var content = new FormUrlEncodedContent(values);
var client = new HttpClient();
var result = client.PostAsync("http://localhost:49923/api/Nltk", content).Result;
Console.Write(result);

How to pass classic asp dictionary to .NET RESTful Web Api

I am having issues passing data parameters from a classic asp application to a .NET Web API. It seems that no matter what I do I cannot access the parameter inside the .NET web API. I have included the code I am using below:
The Classic ASP code:
Public Function GetResponse (HTTPMethod, ReqURL, QueryParamsOrArgs)
Set XMLHTTP = Server.CreateObject("MSXML2.ServerXMLHTTP")
If HTTPMethod = "GET" Then
ReqURL = ReqURL & "?" & QueryParamsOrArgs
QueryParamsOrArgs = ""
End If
XMLHTTP.open HTTPMethod , ReqURL, false
XMLHTTP.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
XMLHTTP.setRequestHeader "AuthToken", ServiceUrl_AuthToken
XMLHTTP.send(QueryParamsOrArgs)
If XMLHTTP.Status = 200 Then
GetResponse = XMLHTTP.responseText
Else
GetResponse = ""
End If
End Function
Public Function TestPOST(url)
mydata = "value1=1&value2=2&value3=3"
TestPOST = GetResponse("POST", url, mydata)
End Function
In the calling ASP page:
dim oDataUtils : Set oDataUtils = (New DataUtils)
myResponse = oDataUtils.TestPost(ServiceUrl_Base & "EventData/Post")
The .NET Web API Action Method:
[HttpPost]
public HttpResponseMessage Post([FromBody]string value)
{
StringContent sc = new StringContent(value);
sc.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage resp = new HttpResponseMessage();
resp.Content = sc;
return resp;
}
No matter what I send, the value of the parameter "value" inside the api method is always null. Ultimately I would love to be able to send an entire dictionary or parameters to this method, but cannot even get the most simple component (a string) to pass in. What am I missing here?
My ultimate goal would be for something like this to work:
[HttpPost]
public HttpResponseMessage Post(Dictionary<string, object> data)
{
// Do something with the dictionary and then return a response...
StringContent sc = new StringContent("test");
sc.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage resp = new HttpResponseMessage();
resp.Content = sc;
return resp;
}
but I will take either option at this point...
The "issue" is: [FromBody]
public HttpResponseMessage Post([FromBody]string value)
It is for "simple" type, and translated means, just one value. Additionally, the format the API expects using FromBody is =value (notice the missing "key").
e.g.
foo=bar //this will fail "null"
=bar //this is good (no key)
This will explain it in detail even if the topic is about jquery, it will give you insight to the behavior of FromBody.
Use FormDataCollection...
Hth....
It might work if you pass proper json instead of just string ,
form you json like
mydata="{'value':'value1=1&value2=2&value3=3'}"
You can also pass dictionary of like
var data={};
data.Value1="1";
data.Value2="2";
data.Value3="3";
Remember the name of param in action method and name of json key must be same,here 'values
mydata={values:data};
[HttpPost]
public HttpResponseMessage Post(Dictionary<string, string> values)

Categories