I use HttpLogging to log requests coming to my endpoints. I would like to log the whole request. I setup the HttpLogging in Program.cs
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpLogging();
And change logging level in appsettings.Development.json
"Microsoft.AspNetCore": "Information"
Then when I send a request from Postman or cURL I can see all the needed information but the RequestBody is ALWAYS empty (near the log end)
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/1.1
Method: POST
Scheme: https
PathBase:
Path: /WeatherForecast
Accept: */*
Connection: keep-alive
Host: localhost:7269
User-Agent: PostmanRuntime/7.29.0
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 60
Postman-Token: [Redacted]
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'WebApplication1.Controllers.WeatherForecastController.Post (WebApplication1)'
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Route matched with {action = "Post", controller = "WeatherForecast"}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[WebApplication1.WeatherForecast] Post() on controller WebApplication1.Controllers.WeatherForecastController (WebApplication1).
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'WebApplication1.WeatherForecast[]'.
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: application/json; charset=utf-8
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed action WebApplication1.Controllers.WeatherForecastController.Post (WebApplication1) in 8.0904ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'WebApplication1.Controllers.WeatherForecastController.Post (WebApplication1)'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[3]
RequestBody: //<----------------------------------------------- ALWAYS EMPTY
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
ResponseBody: [{"date":"2022-03-17T18:05:11.5799408+01:00","temperatureC":41,"temperatureF":105,"summary":"Bracing"}]
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 POST https://localhost:7269/WeatherForecast application/json 60 - 200 - application/json;+charset=utf-8 19.0485ms
However I know the body is there because when I try to read the body in controller and stop on breakpoint I can see the body loaded in the variable.
using var reader = new StreamReader(HttpContext.Request.Body);
var myBody = await reader.ReadToEndAsync(); // {"username": "josef","password": "MyPassword"}
There is a request cURL example generated from PostMan.
curl --location --request POST 'https://localhost:7269/WeatherForecast' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "josef",
"password": "MyPassword"
}'
I dont know if there is a step I missed or something like that. I tried changing different log levels, different request types, bodies and body types. The RequestBody is always empty in the log.
Edit 1
I just found that when I keep the body reader from above in controller and read from it, it actually log the correct RequestBody in the log.
Could someone explain this behaviour? I dont understand why it should log only when I manually read the body.
I have got this working (allbeit with Serilog over the top). I have these additions to your work in my code:
Registering the logging settings:
services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add(HeaderNames.Accept);
logging.RequestHeaders.Add(HeaderNames.ContentType);
logging.RequestHeaders.Add(HeaderNames.ContentDisposition);
logging.RequestHeaders.Add(HeaderNames.ContentEncoding);
logging.RequestHeaders.Add(HeaderNames.ContentLength);
logging.MediaTypeOptions.AddText("application/json");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
Including some appsettings.json:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Information",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
}
},
Probably just the appsettings are needed, although you definately need to code logging.MediaTypeOptions.AddText("multipart/form-data"); if you want a request form submission logged
Related
I've been battling this issue now for around 30 hours, and I just cannot seem to get by it. I'm having an issue with CORS in a .NET CORE 3.0.1 WebAPI project throwing the following error when called by my Angular project:
Access to XMLHttpRequest at 'http://localhost:4200/#/emailverified'
(redirected from 'http://localhost:5000/api/auth/forgotpassword') from
origin 'http://localhost:4200' has been blocked by CORS policy:
Request header field content-type is not allowed by
Access-Control-Allow-Headers in preflight response.
I have CORS setup in my startup.cs file as follows:
ConfigureServices method in Startup.cs:
services.AddCors(options => options.AddPolicy("CorsPolicy", p => p
.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.WithHeaders("content-type")
.AllowCredentials()
));
Configure method in Startup.cs:
app.UseCors("CorsPolicy");
The API Call that is being made from the Angular service
resetpassword(model: any) {
return this.http.post(this.baseUrl + 'forgotpassword', model);
}
The controller:
[AllowAnonymous]
[EnableCors("CorsPolicy")]
[HttpPost("forgotpassword")]
public async Task<IActionResult> ForgotPassword(SendPasswordResetDto sendPasswordReset)
{
if (sendPasswordReset.Email == null)
{
return NotFound("Please enter a valid email address");
}
var user = await _userManager.FindByEmailAsync(sendPasswordReset.Email);
// If the email does not exist, return null
if (user == null)
{
return Redirect($"{_configuration["ViewUrl"]}/#/emailverified");
}
// If the user email does exist, create a password reset token and encode it in a browser friendly way
var forgotPasswordToken = await _userManager.GeneratePasswordResetTokenAsync(user);
var encodedToken = Encoding.UTF8.GetBytes(forgotPasswordToken);
var validToken = WebEncoders.Base64UrlEncode(encodedToken);
// Send an email to the user with the reset email link
string url = $"{_configuration["ViewUrl"]}/#/changepassword?email={user.Email}&token={validToken}";
await _MailRepository.SendEmailAsync(user.Email, "Password Reset", $"<h1>You have requested to reset your password.</h1> <p>Please click <a herf='{url}'>this link</a> to reset your password. If it does not work, please copy and paste this link into your browser " + url + "</p>");
return Redirect($"{_configuration["ViewUrl"]}/#/emailverified");
}
And lastly these are the headers being sent with the request:
Request URL: http://localhost:4200/
Request Method: OPTIONS
Status Code: 200 OK
Remote Address: 127.0.0.1:4200
Referrer Policy: no-referrer-when-downgrade
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:5000
Origin: http://localhost:4200
Pragma: no-cache
Referer: http://localhost:4200/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
I seriously cannot get past this and it is driving me absolutely nuts. Is there something I am missing here? I have added the "content-type" header to the CORS policy directly, but it just ignores it. I have searched high and low, and attempted about 2 dozen different ways of setting this up in my project. I've also tried clearing browser cache and using different browsers that have not been used on this project yet in case the cache was affecting it, and looking at the placement of app.UseCors("CorsPolicy") within the configure method, nothing seems to be getting this to work.
Is there something anyone can spot that I have missed?
CORS seems to work just fine everywhere else within the project except when using the following statement.
return Redirect($"{_configuration["ViewUrl"]}/#/emailverified");
So after fighting with this for the better part of a week, I have decided not to use the redirect return statement in the controller and instead just let the front end angular project handle the redirect based off of the value of the return from he API.
I am experimenting with the REST API for sending batch messages in Firebase Cloud Messaging. I prepared a multipart HTTP request in C#:
using var request = new HttpRequestMessage(HttpMethod.Post, "https://fcm.googleapis.com/batch");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "xxx");
request.Content = new StringContent(multicast);
request.Content.Headers.Remove("Content-Type");
request.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=--subrequest_boundary");
var response = await FcmHttpClient.SendAsync(request);
The string value of the multicast field above is an HTTP content similar the one provided in the Firebase documentation:
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer xxx
POST /v1/projects/myproject/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"topic":"global",
"notification":{
"title":"FCM Message",
"body":"This is an FCM notification message to device 0!"
}
}
}
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer xxx
POST /v1/projects/myproject/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"topic":"readers-club",
"notification":{
"title":"Price drop",
"body":"2% off all books"
}
}
}
--subrequest_boundary--
Firebase server returns Bad Request-400 with error message: "Failed to parse batch request, error: 0 items. Received batch body: --subrequest_boundary--" which indicates that Firebase directly handles the content with the terminating --subrequest_boundary--.
What could be the cause of the problem?
yesterday, i need write bash script to send bath FCM notifaction, and see your code #Ugur, thanks.
now its working, u need change
Content-Type", "multipart/mixed; boundary=--subrequest_boundary
to
Content-Type", "multipart/mixed; boundary=subrequest_boundary
the script
#!/bin/bash
curl \
-X POST \
-H "Authorization: Bearer [token_auth]" \
-H "Content-Type: multipart/mixed; boundary=subrequest_boundary" \
--data-binary #test2.txt \
https://fcm.googleapis.com/batch
and test2.txt, example send 2 notification
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer [token_auth]
POST /v1/projects/[project_name_firebase]/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"token":"[token_device]",
"notification":{
"title":"FCM Message",
"body":"This is an FCM notification message to device 0!",
}
}
}
--subrequest_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Authorization: Bearer [token_auth]
POST /v1/projects/[project_name_firebase]/messages:send
Content-Type: application/json
accept: application/json
{
"message":{
"token":"[token_device]",
"notification":{
"title":"FCM Message",
"body":"This is an FCM notification message to device 0!",
}
}
}
--subrequest_boundary--
change [project_name_firebase] with name your project in firebase
console example : project_3323.
change [token_device] with token target device.
change [token_auth] with your google auth credentials token.
Notification configuration: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#AndroidConfig
to get [token_auth]
java :
https://developers.google.com/identity/protocols/oauth2/service-account#java_1
phyton:
https://developers.google.com/identity/protocols/oauth2/service-account#python_2
curl:
https://developers.google.com/identity/protocols/oauth2/service-account#httprest
code for get [token_auth], getToken.sh
#!/bin/bash
client_email="[email_project_firebase]"
service="https://oauth2.googleapis.com/token"
time=3600
scope="https://www.googleapis.com/auth/cloud-platform"
private_key=$(echo -ne "[private_key]")
datenow="$(date +%s)"
expired="$(( datenow + time ))";
header='{"alg": "RS256","typ": "JWT"}'
payload='{"iss": "'$client_email'","scope": "'$scope'","aud": "'$service'","exp": '$expired',"iat": '$datenow'}'
HEADEREnc=$( echo -n "${header}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
PAYLOADEnc=$( echo -n "${payload}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
data="${HEADEREnc}"."${PAYLOADEnc}"
signature=$( openssl dgst -sha256 -sign <(echo -n "${private_key}") <(echo -n "${data}") | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
JWT="${data}"."${signature}"
jsonDataToken=$(curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='$JWT https://oauth2.googleapis.com/token)
tokenAuth=$(echo -n $jsonDataToken |grep -Po '"access_token":.*?[^\\]"'|cut -d ':' -f2 |sed 's/"//g')
echo $tokenAuth
[email_project_firebase],[private_key] u can get it from file json in "firebase console" -> project name -> project setting or click gear icon -> account services -> click button "create new secret key"
list scope for other service
https://developers.google.com/identity/protocols/oauth2/scopes
Try to change your code
request.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=--subrequest_boundary");
to
request.Content.Headers.TryAddWithoutValidation("Content-Type", "multipart/mixed; boundary=subrequest_boundary");
The devil's in the details of building the Content. You can't simply create a string matching that payload, because certain pieces of it, like the headers and boundaries, are not technically considered part of the body by the HttpClient stack.
If you really need to do this with a raw HttpClient, take a look at how the Google APIs .NET client builds and sends a batch request. But I suspect that once you realize how cumbersome this is, you're going to conclude that you're best off using a higher-level SDK, such as firebase-admin-dotnet, which is mentioned in the same documentation you linked to and utilizes Google's .NET client under the hood.
I receive the following response when trying to consume text/plain:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
"title": "Unsupported Media Type",
"status": 415,
"traceId": "|b28d0526-4ca38d2ff7036964."
}
Controller definition:
[HttpPost]
[Consumes("text/plain")]
public async Task<IActionResult> PostTrace([FromBody]string body)
{ ... }
HTTP message:
POST /api/traces HTTP/1.1
Content-Type: text/plain
User-Agent: PostmanRuntime/7.19.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 37d27eb6-92a0-4a6a-8b39-adf2c93955ee
Host: 0.0.0.0:6677
Accept-Encoding: gzip, deflate
Content-Length: 3
Connection: keep-alive
I am able to consume JSON or XML just fine. What am I missing?
Reference: Accepting Raw Request Body Content in ASP.NET Core API Controllers:
Unfortunately ASP.NET Core doesn't let you just capture 'raw' data in any meaningful way just by way of method parameters. One way or another you need to do some custom processing of the Request.Body to get the raw data out and then deserialize it.
You can capture the raw Request.Body and read the raw buffer out of that which is pretty straight forward.
The easiest and least intrusive, but not so obvious way to do this is to have a method that accepts POST or PUT data without parameters and then read the raw data from Request.Body:
[HttpPost]
[Route("api/BodyTypes/ReadStringDataManual")]
public async Task<string> ReadStringDataManual()
{
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
Request:
POST http://localhost:5000/api/BodyTypes/ReadStringDataManual HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/plain
Host: localhost:5000
Content-Length: 37
Expect: 100-continue
Connection: Keep-Alive
Windy Rivers with Waves are the best!
I am experiencing the exact same error as described in question INVALID_REQUEST_PARAMETER on listStatus
However, unlike that OP, I am not using the REST API directly, but am using the C# SDK from https://www.nuget.org/packages/DocuSign.eSign.dll
It would appear that the SDK wrapper is not including the querystring parameters as described by the answer in the above linked post. Is there a workaround other than waiting for DocuSign to fix their SDK -- and where is the appropriate place to submit a bug for their SDK?
Per comment, here is a code sample:
var envelopesApi = new DocuSign.eSign.Api.EnvelopesApi();
var envelopeIds = incentivesWithPendingOffers.Select(i => i.new_OfferLetterEnvelopeID).ToList();
var envelopeInfos = await envelopesApi.ListStatusAsync(_tokenAccountId, new EnvelopeIdsRequest(envelopeIds), null);
Running fiddler to capture the outbound REST call being made by the SDK, I see this:
PUT https://demo.docusign.net/restapi/v2/accounts/[ REDACTED ]/envelopes/status HTTP/1.1
X-DocuSign-SDK: C#
Authorization: Bearer [ REDACTED ]
Accept: application/json
User-Agent: Swagger-Codegen/2.1.0/csharp
Content-Type: application/json
Host: demo.docusign.net
Content-Length: 96
Accept-Encoding: gzip, deflate
{"envelope_ids":["1d324bac-60ea-44b5-9b60-a5de14af3beb","5431d728-4918-4218-9c12-765b1c914724"]}
which returns the following response (which the SDK turns into a .NET Exception):
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Content-Length: 238
Content-Type: application/json; charset=utf-8
X-DocuSign-TraceToken: [ REDACTED ]
Date: Wed, 01 Aug 2018 20:43:58 GMT
Strict-Transport-Security: max-age=31536000; includeSubDomains
{
"errorCode": "INVALID_REQUEST_PARAMETER",
"message": "The request contained at least one invalid parameter. Query parameter 'from_date' must be set to a valid DateTime, or 'envelope_ids' or 'transaction_ids' must be specified."
}
When the previous answer was written, the SDK didn't support putting the list of envelope IDs in the call body. As of client version 3.1.3 this is now available.
List<string> envelopeIds = new List<string>();
envelopeIds.Add("2b62eb63-784a-4228-be02-876762ea6661");
envelopeIds.Add("406a9a15-c8e9-4227-8dd2-bd9a5318d4fd");
EnvelopeIdsRequest envelopeIdsRequest = new EnvelopeIdsRequest();
envelopeIdsRequest.EnvelopeIds = envelopeIds;
ListStatusOptions options = new ListStatusOptions();
options.envelopeIds = "request_body"; //the Options value controls the query string parameter
EnvelopesInformation envelopesInfo = envelopesApi.ListStatus(accountId, envelopeIdsRequest, options);
I wasn't able to use the envelope_ids=request_body parameter via the SDK, but I was able to get status of several envelopes at once. This would be a viable workaround as long as you're not requesting so many envelope IDs that the URL overflows.
EnvelopesApi.ListStatusChangesOptions lsco = new EnvelopesApi.ListStatusChangesOptions
{
envelopeIds = "fdd1122a-9c1b-4eef-9e24-25bb2cdf2eb2, fe1cb500-6a4c-4328-bf24-55806434852f, 5b1d3828-f8cd-4bba-87f0-538cb920db96"
};
EnvelopesInformation listStatusChanges = envelopesApi.ListStatusChanges(accountId, lsco);
results in an API call to
GET https://demo.docusign.net/restapi/v2/accounts/{{accountId}}/envelopes?envelope_ids=fdd1122a-9c1b-4eef-9e24-25bb2cdf2eb2%2C%20fe1cb500-6a4c-4328-bf24-55806434852f%2C%205b1d3828-f8cd-4bba-87f0-538cb920db96
I have a controller that looks similar to the following:
[HttpPut]
[Route("")]
public async Task<IActionResult> Put([FromBody]List<MyObject> fromBody)
{
if (fromBody == null)
throw new InvalidOperationException($"{nameof(fromBody)} must not be null");
// Unimportant junk
}
Very rarely, the InvalidOperationException is thrown during a request. But the raw request data has the correct content and headers(I use raygun for exception reporting and it captures the raw request and the json content is there and valid). Are there any reasons that this could occur?
The headers are the same between both a successful request and a bad one, and are as follows(With my website and any Azure id's redacted):
Connection: "Keep-Alive"
Content-Type: "application/json;charset=utf-8"
Accept: "application/json;charset=utf-8"
Accept-Encoding: "gzip"
Host: "redacted"
Max-Forwards: "10"
User-Agent: "Dalvik/2.1.0 (Linux; U; Android 6.0.1; Nexus 5 Build/M4B30Z)"
Content-Length: "55392"
X-WAWS-Unencoded-URL: "redacted"
X-Original-URL: "redacted"
X-ARR-LOG-ID: "redacted"
DISGUISED-HOST: "redacted"
X-SITE-DEPLOYMENT-ID: "redacted"
WAS-DEFAULT-HOSTNAME: "redacted"
X-Forwarded-For: "redacted"
X-ARR-SSL: "redacted"
X-Forwarded-Proto: "https"
MS-ASPNETCORE-TOKEN: "redacted"
X-Original-For: "redacted"
X-Original-Proto: "http"
Unfortunately I cannot disclose the actual body of the message due to policies beyond my control, but it is composed of a 100% valid json array of objects totaling in the correct length in regard to the Content-Length. And when my client does a retry with the same content/headers, it succeeds.
My suspicion is that it is potentially timed out requests still entering the controller, and then being unable to read the request body due to the timeout. Clients are android devices so they go in and out of internet connectivity constantly. But I don't know why it would enter the controller at all in that case.
Turns out that Asp.NET core had a bug that resulted in this behavior.
https://github.com/aspnet/Mvc/issues/7551
It has been patched in 2.1.X+ of which a preview release is available.