How do I include content with a GET request? - c#

EDIT: Please note, I know the heart of the problem lies in the service I need to communicate with is not following protocol. This is software which I am not capable of touching and will not be changed anytime soon. Thus, I need help with circumventing the problem and breaking protocol. Development at its finest!
I'm attempting to communicate with an external service. Whomever made it decided to split various calls into not just different folders, but also HTTP request types. The problem here, is that I need to send a GET request that includes content.
Yes, this violates the protocol.
Yes, this works if I formulate the call using Linux commands.
Yes, this works if I manually craft the call in Fiddler (although Fiddler gets angry at the breach of protocol)
When I craft my call, it's wrapped in an async method. Sending it, however, results in an error:
Exception thrown: 'System.Net.ProtocolViolationException' in mscorlib.dll ("Cannot send a content-body with this verb-type.")
Code for the call:
/// <summary>
/// Gets a reading from a sensor
/// </summary>
/// <param name="query">Data query to set data with</param>
/// <returns></returns>
public async Task<string> GetData(string query)
{
var result = string.Empty;
try
{
// Send a GET request with a content containing the query. Don't ask, just accept it
var msg = new HttpRequestMessage(HttpMethod.Get, _dataApiUrl) { Content = new StringContent(query) };
var response = await _httpClient.SendAsync(msg).ConfigureAwait(false);
// Throws exception if baby broke
response.EnsureSuccessStatusCode();
// Convert to something slightly less useless
result = await response.Content.ReadAsStringAsync();
}
catch (Exception exc)
{
// Something broke ¯\_(ツ)_/¯
_logger.ErrorException("Something broke in GetData(). Probably a borked connection.", exc);
}
return result;
}
_httpClient is created in the constructor and is a System.Net.Http.HttpClient.
Does anyone have an idea how to override the regular protocols for the HttpClient and force it to make the call as a GET call, but with a content containing my query for the server?

To me the less devastating way to achieve that is to set ContentBodyNotAllowed field of Get KnownHttpVerb to false using reflection.
You can try with this:
public async Task<string> GetData(string query)
{
var result = string.Empty;
try
{
var KnownHttpVerbType = typeof(System.Net.AuthenticationManager).Assembly.GetTypes().Where(t => t.Name == "KnownHttpVerb").First();
var getVerb = KnownHttpVerbType.GetField("Get", BindingFlags.NonPublic | BindingFlags.Static);
var ContentBodyNotAllowedField = KnownHttpVerbType.GetField("ContentBodyNotAllowed", BindingFlags.NonPublic | BindingFlags.Instance);
ContentBodyNotAllowedField.SetValue(getVerb.GetValue(null), false);
var msg = new HttpRequestMessage(HttpMethod.Get, _dataApiUrl) { Content = new StringContent(query) };
var response = await _httpClient.SendAsync(msg).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
result = await response.Content.ReadAsStringAsync();
}
catch (Exception exc)
{
_logger.ErrorException("Something broke in GetData(). Probably a borked connection.", exc);
}
return result;
}

Related

SendGrid works, but still throws exception not catchable in catch() block

Note: To the moderator that incorrectly closed this question, it's completely different from the generic nullref question. This is specific to SendGrid.
I believe I'm following pretty close to documented SendGrid usage:
public async Task<string> SendEmailSendGrid(string emailTo, string subject, string body) {
var apiKey = SafeTrim(ConfigurationManager.AppSettings["SendGridAPIKey"]);
var client = new SendGridClient(apiKey);
var from = new EmailAddress(SafeTrim(ConfigurationManager.AppSettings["SendGridEmail"]));
var to = new EmailAddress(emailTo);
var msg = MailHelper.CreateSingleEmail(from, to, subject, string.Empty, body);
try {
var response = await client.SendEmailAsync(msg);
//return response;
return "SUCCESS";
} catch (Exception ex) {
return "ERROR in SendEmailSendGrid(): " + ex.Message;
}
}
And the caller:
var result = utils.SendEmailSendGrid(decodedEmail, "email test", "This is a test email using SendGrid.");
And the error I get every time EVEN THOUGH IT WORKS and the email actually sends and arrives in my inbox:
Object reference not set to an instance of an object.
That error occurs on this line:
var response = await client.SendEmailAsync(msg);
I verified that all my variables are populated as expected - none are null or empty. I am passing an empty string to the plain text param (because I always want HTML contents), but I also tried passing that some content and it made no difference; same error.
A strange thing: this blows up so hard that my catch block is never entered. Instead, as soon as the exception is thrown, this full-screen window comes up in my VS2022:
So it is working and sending the email, but why the heavy crash? What am I doing wrong?
The method is awaitable:
public async Task<string> SendEmailSendGrid(...
Yet, the caller is not awaiting the result:
var result = utils.SendEmailSendGrid(decodedEmail, ...
Either await the result:
var result = await utils.SendEmailSendGrid(decodedEmail, ...
Or, if the invoking method is not an async method:
var result = utils.SendEmailSendGrid(decodedEmail, ...).GetAwaiter().GetResult();
Visual Studio is not breaking inside of your try/catch b/c the exception is in the .NET framework by not awaiting the result. You should be able to resolve that by enabling "just my code" in the visual studio debugger settings (IIRC).

How can I return an object that "does not exist in the current context" from a Using statement

So I got a Blazor edit page that needs to update fields. My main issue is trying to return the object "ResponseDto" with the changes made and refresh the screen;
Call from blazor page:
<div class="form-group">
<button class="btn btn-primary edit-btn" style="float: right;" #onclick="#SaveChanges">Save Changes</button>
</div>
protected async Task SaveChanges()
{
clientdto = await apiService.SaveDtoAsync(ClientID, clientdto);
this.StateHasChanged();
}
API service
public async Task<ClientDto> SaveDtoAsync(int ClientID, [FromBody] ClientDto ClientDto)
{
_httpClient.DefaultRequestHeaders.Clear();
var Url = "api/clients/" + Convert.ToInt32(ClientID);
var SerializedClientDto = JsonConvert.SerializeObject(ClientDto);
using (var RequestClientDto = new HttpRequestMessage(HttpMethod.Put, Url))
{
RequestClientDto.Content = new StringContent(SerializedClientDto, System.Text.Encoding.UTF8, "application/json");
RequestClientDto.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
using (var ResponseClientDto = await _httpClient.SendAsync(RequestClientDto))
{
if (!ResponseClientDto.IsSuccessStatusCode)
{
ResponseClientDto.EnsureSuccessStatusCode();
}
else
{
var Response = await ResponseClientDto.Content.ReadAsStringAsync();
var ResponseDto = JsonConvert.DeserializeObject<ClientDto>(Response);
}
}
}
return ResponseDto;
}
This doesn't have anything to do with Blazor, you're simply trying to use a variable in a higher scope than it exists.
But before you can decide on a correction, first you need to decide what the method should return if you never populate ResponseDto. For example, if it should throw an exception then you can move the return to where you define the variable (don't need the variable at all, really) and throw an exception at the end of the method:
//...
else
{
var Response = await ResponseClientDto.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ClientDto>(Response);
}
}
}
throw new Exception("Some meaningful error message");
}
Or if it should return null or an empty instance of the type, you can do that:
//...
else
{
var Response = await ResponseClientDto.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ClientDto>(Response);
}
}
}
return null; // or: return new ClientDto();
}
It's really up to you how you want the method to behave, and there are a variety of ways to structure it. All you need to ensure is:
You use variables in the scope that they exist.
All possible logical code paths in the method produce a result. (Either return a value or throw an exception.)
Here is a somewhat simplified version and should be equal to the call above.
public async Task<ClientDto> SaveDtoAsync(int clientID, [FromBody] ClientDto clientDto)
{
_httpClient.DefaultRequestHeaders.Clear();
var response = await _httpClient.PutAsJsonAsync(url, $"api/clients/{clientID}");
if (!response.IsSuccessStatusCode)
{
//TODO: add error handling
return null; //or default;
}
using (MemoryStream ms = await response.Content.ReadAsStreamAsync())
{
return await JsonSerializer.DeserializeAsync<ClientDto>(ms);
}
}
I'm using the Http extension methods that can make http calls more readable and manageble. The other trick to note here I'm not reading the response content as string, instead I use memory stream. With new Json parser we can safe some allocations this way. This means less GC pressure. (Small side note for parsing large json (over 1000 objects) there is known performance bug).
For a less change required version #David already posted an excelente answear.
#ZoltBendes Thank you so much for the help, I managed to get it to work "partially", It runs but I get back a "StatusCode: 204, ReasonPhrase: 'No Content' so technically its not taking the changes even though everything seems to be working fine. This is how I got to work, once again "partially"
HttpResponseMessage ResponseClientDto = await httpClient.PutAsJsonAsync($"api/clients/{ClientID}", ClientDto);
if (!ResponseClientDto.IsSuccessStatusCode)
{
ResponseClientDto.EnsureSuccessStatusCode();
}
var response = await _httpClient.GetAsync($"api/clients/{ClientID}");
var responseContent = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ClientDto>(responseContent);

Flurl Client - Is it possible to access the headers from a failed request?

I am using Flurl Client to call a restful API with a post data. There is a validation performed on the server on the data I submit and it returns back a header containing an error message for the user.
As the request requirement doesn't satisfy server marks the request as 400 BadRequest. In the below code on line cli.Request(uri).PostJsonAsync(data) it throws the FlurlHttpException with appropriate status code.
Now, as there is a problem with the input data by the user I want to report the user back with the error message which I receive from the server in the header. However, I am unable to access the response headers as the request has failed.
Is there any other way to access the response headers from a failed request using Flurl?
try
{
using (var cli = new FlurlClient(baseUrl))
{
var httpResponse = await cli.Request(uri).PostJsonAsync(data);
var errorMessage = httpResponse.GetHeaderValue("errorMessage");
}
}
catch (FlurlHttpException ex)
{
}
Using an event handler works, but I think those are better for cross-cutting concerns like logging that you don't want cluttering the main flow of your app. You basically want to allow and/or handle 400 responses as part of that main flow. You can do that more directly with AllowHtttpStatus, which can be set on the client:
cli.AllowHtttpStatus(HttpStatusCode.BadRequest);
or the request:
var httpResponse = await cli
.Request(uri)
.AllowHttpStatus(HttpStatusCode.BadRequest)
.PostJsonAsync(data);
Either way, the call will not throw on a 400.
Another way to do this, and one I'd recommend if your app logic takes a completely different path on an error condition than on a success condition, is to keep your try/catch in place and use the Response property of the exception to handle the error condition:
try
{
await cli.Request(uri).PostJsonAsync(data);
// handle success condition
}
catch (FlurlHttpException ex) when (ex.Response?.StatusCode == 400)
{
var errorMessage = ex.Response.GetHeaderValue("errorMessage");
// handle error condition
}
As a side note, there are some significant changes coming in 3.0 that you should be aware of as they touch on some of these areas directly:
https://github.com/tmenier/Flurl/issues/354
https://github.com/tmenier/Flurl/issues/488
I am configuring the Error Event Handler to report any error. As a result, the code doesn't jump to the exception block it asynchronously fires the event handler, and the rest of my subsequent code executes OK with an appropriate httpResponseMessage, StatusCode, headers everything.
...
FlurlHttp.Configure(settings => settings.OnErrorAsync = HandleFlurlErrorAsync);
using (var cli = new FlurlClient(baseUrl))
{
var httpResponse = await cli.Request(uri).PostJsonAsync(data);
var errorMessage = httpResponse.GetHeaderValue("errorMessage");
}
...
private async Task HandleFlurlErrorAsync(HttpCall call)
{
//Log your exception here
call.ExceptionHandled = true;
}

async/await seems to hang inside web method

In C# I have a web service with an operation result defined as below:
OperationResult<createAccountResponse> CreateAccount(string token, createAccountServiceModel model);
Inside that method I call another method with a signature indicating it is async, like so:
var sendEmailInvite = this.SendExhibitorInviteEmailAsync(new ExhibitorInviteEmailPartialRequest()
{
CompanyId = company.CompanyID,
EventId = event.EventID
});
And inside SendExhibitorInviteEmailAsync I await a method which is also marked as async. Here is that method (snipped for brevity)
public async Task<ExhibitorInviteEmailResponse> SendExhibitorInviteEmailAsync(ExhibitorInviteEmailResolvedRequest request)
{
ExhibitorInviteEmailResponse response = null;
try
{
response = new ExhibitorInviteEmailResponse();
var apiKey = "snip";
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage();
msg.SetFrom(new EmailAddress("noreply#domain.com", "Display name"));
msg.AddTo(new EmailAddress(request.EmailAddress, request.AccountOwnerFirstName));
msg.SetTemplateId("snipped");
\
msg.SetTemplateData(dynamicTemplateData);
await client.SendEmailAsync(msg);
}
catch (Exception ex)
{
response = new ExhibitorInviteEmailResponse
{
Success = false,
Error = true,
ErrorMessage = ex.Message
};
}
return response;
}
If the email is meant to be sent (flag field in the json) then I start working on the email.If no email is meant to be sent, the whole method takes about a second which was what it was before.
The issue I am having is when I run this method from Postman or from C# generated by Postman, it seems the async code for sending the email causes the duration of the request to be 30+ seconds - so it seems like something is not waiting for the email to send? When I run this via a browser it works in 1-2 seconds with no delay.
What is the recommended flow when using Postman and async? Do all internal method's parents have to await as well?

How to acquire HttpStatus Codes in ASP.Net Core?

I'm running through a list of both secure and unsecured domains (both http:// and https://) in an array and wish to return their status codes. How is this possible in ASP.Net Core 1.0?
so far, I have
foreach(var item in _context.URLs.ToList())
{
// Do something here with item.Domain to check for status
// For example, if item.Domain was https://example.com..
}
I tried it with regular ASP.Net syntax with this approach:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(item.Domain);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
The problem is, GetResponse doesn't work in ASP.Net Core
Can anyone help me out with an effecient solution so that the variable returned would be the status?
ex: 200, 500, 404..
EDIT - Here is my full controller AND SOLUTION:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using MyApp.Models;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
namespace MyApp.Controllers.Api
{
public class URLsController : Controller
{
private MyAppDBContext _context;
public class newLink
{
public string Domain { get; set; }
public HttpStatusCode Status { get; set; }
}
public async Task<HttpStatusCode> GetStatusCodes(string url)
{
var client = new HttpClient();
var response = await client.GetAsync(url);
return response.StatusCode;
}
public URLsController(MyAppDBContext context)
{
_context = context;
}
[HttpPost("api/URLs")]
public async Task<IActionResult> Post(string url)
{
if (url != "")
{
// I pass a URL in through this API and add it to _context.URLs..
// I execute a status code check on the URLs after this if statement
}
List<newLink> list = new List<newLink> ();
foreach (var item in _context.URLs.ToList())
{
newLink t = new newLink();
t.Domain = item.Domain;
t.Status = await GetStatusCodes(item.Domain);
list.Add(t);
}
return Ok(list);
}
}
}
This returns an array back in this format:
[{"Domain":"https://example1.com/","Status":200},
{"Domain":"https://example2.com/","Status":200},
{"Domain":"https://example3.com/","Status":200}]
You could use HttpClient as it is easier to work with (you don't need to catch WebExceptions for the non success status codes like you would have to do with the plain HttpWebRequest and then extract the HTTP status code from the exception).
You could write a helper method that given a list of urls will return a list of corresponding status codes. This will make your code a little bit more decoupled. Do not violate the single responsibility principle. A method should not do more than 1 specific thing (in your example you were mixing some DB calls and HTTP calls into a single method which is bad practice).
public async Task<IList<HttpStatusCode>> GetStatusCodes(IList<string> urls)
{
var client = new HttpClient();
var result = new List<HttpStatusCode>();
foreach (var url in urls)
{
var response = await client.GetAsync(url);
result.Add(response.StatusCode);
}
return result;
}
Remark 1: If the url that you are trying to call is not DNS resolvable or your calling application doesn't have network access to the specified address on the target port you will not get any status code for obvious reasons. You will get a nice exception. Think about handling this case. It's up to you to decide what you want to return in the resulting collection in this case.
Remark 2: Making a GET request just for determining the HTTP status code might be a waste as you are throwing away the response body that you have already transported over the wire. If the remote resource responds to HEAD requests that might be more efficient way to determine if the server is alive. But consider this with caution as it will depend on the specifics of the web endpoint that you are calling.
Remark 3: You have undoubtedly noticed that this method is async. Well, if you intend to develop under .NET Core you'd better get accustomed to it. You could of course violate the built-in asynchronous patterns that the framework provides you by blocking the calling thread:
var urls = _context.URLs.ToList();
IList<HttpStatusCode> statusCodes = GetStatusCodes(urls).GetAwaiter().GetResult();
But this is an extremely bad practice. A more idiomatic way of working is to make all your methods asynchronous through the entire chain until you reach the main calling method which is usually provided by the framework itself and which can be asynchronous as well. For example if you are calling this inside a Web API action you could simply make it async:
[HttpGet]
[Route("api/foos")]
public async Task<IActionResult> Get()
{
var urls = _context.URLs.ToList();
IList<HttpStatusCode> statusCodes = await GetStatusCodes(urls);
return this.Ok(statusCodes);
}

Categories

Resources