I have been using two different Azure functions to talk to each other, one is an HttpTrigger contained in one app service and another is a TimerTrigger contained in another app service. The HttpTrigger uses Function-level authentication.
Here is my HttpTrigger function:
[FunctionName("PendingOrders")]
public static async Task<HttpResponseMessage> GetPendingOrderIds(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "pending-orders")]
HttpRequestMessage req,
TraceWriter log,
[Inject] OrdersController controller)
{
Calling it with the master key works in a browser, but not with RestSharp. The only thing I changed since the previous deployment was upgrading RestSharp from 106.3.1 to 106.6.9. Not a major-version change so there shouldn't be any breaking changes.
private static IRestResponse ProcessRequest(string url, Method method, object requestBody)
{
// Variable "url" is the path plus `?code=<master key here>
var client = PrepareRequest(url, method, requestBody); // see below
return client.Execute(); // executes here, returns 401 Unauthorized.
}
private static PreparedClient PrepareRequest(string url, Method method, object requestBody)
{
var uri = new Uri(url);
var client = new RestClient(uri.GetLeftPart(UriPartial.Authority));
var request = new RestRequest(uri.PathAndQuery, method)
{
OnBeforeDeserialization = resp =>
{
resp.ContentType = "application/json";
}
};
if (requestBody != null)
{
request.AddJsonBody(requestBody);
}
return PreparedClient.Prepare(client, request);
}
Where PreparedClient is
internal struct PreparedClient
{
public IRestClient Client { get; private set; }
public IRestRequest Request { get; private set; }
public static PreparedClient Prepare(IRestClient client, IRestRequest request)
{
var prepared = new PreparedClient { Client = client, Request = request };
return prepared;
}
public IRestResponse Execute()
{
return Client.Execute(Request);
}
public async Task<IRestResponse> ExecuteAsync()
{
return await Client.ExecuteTaskAsync(Request);
}
}
I have debugged the code and verified that the ?code=xxx master key is being added to the url. Is there a different way of authenticating that I need to use that changed between RestSharp 106.3.1 and 106.6.9? Does something have to be put in the headers?
Related
I have an ASP.NET MVC application which invokes an ASP.NET Web API REST Service each time a button is pressed in the UI.
Each time this button is pressed below DumpWarehouseDataIntoFile method is executed.
public class MyClass
{
private static HttpClient client = new HttpClient();
public async Task DumpWarehouseDataIntoFile(Warehouse myData, string path, string filename)
{
try
{
//Hosted web API REST Service base url
string Baseurl = "http://XXX.XXX.XX.X:YYYY/";
//using (var client = new HttpClient()) --> I have declared client as an static variable
//{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Serialize parameter to pass to the asp web api rest service
string jsonParam = Newtonsoft.JsonConvert.SerializeObject(myData);
//Sending request to find web api REST service resource using HttpClient
var httpContent = new StringContent(jsonParam, Encoding.UTF8, "application/json");
HttpResponseMessage Res = await client.PostAsync("api/Warehouse/DumpIntoFile", httpContent);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
// Some other sftuff here
}
//}
}
catch (Exception ex)
{
// Do some stuff here
} // End Try
} // End DumpWarehouseDataIntoFile method
} // End class
Warehouse class object:
public class Warehouse
{
public DataTable dt { get; set; }
public string Filepath { get; set; }
}
I have found in this post that pattern:
using (var myClient = new HttpClient())
{
}
is not recommended to be used since it leads to socket exhaustion (System.Net.Sockets.SocketException). There it is recommended to use HttpClient as static variable and reuse it as it helps to reduce waste of sockets. So I have used a static variable.
The problem with this approach (in my scenario) is that it only works first button is pressed, next times button is pressed and DumpWarehouseDataIntoFile method is executed, below exception is thrown:
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: This instance has already started
one or more requests. Properties can only be modified before sending
the first request.
As error says, properties like base address, etc. can only be modified once before sending the first request.
I have googled and found some solutions proposed:
First solution
So it seems like singleton pattern would be a good option, as proposed here. Below the singleton proposed by Alper:
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class MyApiClient : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "my-api-client-v1";
private const string MediaTypeJson = "application/json";
public MyApiClient(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task<string> PostAsync(string url, object input)
{
EnsureHttpClientCreated();
using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
{
using (var response = await _httpClient.PostAsync(url, requestContent))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
{
var strResponse = await PostAsync(url, input);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
{
var strResponse = await GetAsync(url);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<string> GetAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PutAsync(string url, object input)
{
return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
}
public async Task<string> PutAsync(string url, HttpContent content)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.PutAsync(url, content))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> DeleteAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.DeleteAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
Usage
using (var client = new MyApiClient("http://localhost:8080"))
{
var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
The problem I see here is that if you call above code many times (in my case would be each time I press the button on the UI and I call DumpWarehouseDataIntoFile method), you create and instance of MyApiClient each time and therefore a new instance of HttpClient is created and I want to reuse HttpClient, not to make many instances of it.
Second solution
Creating a kind of factory as proposed here by Nico. Below the code he proposes:
public interface IHttpClientFactory
{
HttpClient CreateClient();
}
public class HttpClientFactory : IHttpClientFactory
{
static string baseAddress = "http://example.com";
public HttpClient CreateClient()
{
var client = new HttpClient();
SetupClientDefaults(client);
return client;
}
protected virtual void SetupClientDefaults(HttpClient client)
{
client.Timeout = TimeSpan.FromSeconds(30); //set your own timeout.
client.BaseAddress = new Uri(baseAddress);
}
}
Usage
public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
readonly IHttpClientFactory _httpClientFactory;
public IActionResult Index()
{
var client = _httpClientFactory.CreateClient();
//....do your code
return View();
}
Here again you create a new instance of HttpClient each time you call CreateClient. You do not reuse HttpClient object.
Third Solution
Making HTTP requests using IHttpClientFactory as explained here.
The problem is that it is only available for .NET Core, not standard ASP.NET Framework, though it seems it is available by installing this nuget package. It seems like it automatically manages efficiently HttpClient instances and I would like to apply it to my scenario. I want to avoid to
reinvent the wheel.
I have never used IHttpClientFactory and I have no idea on how to use it: configure some features like base address, set request headers, create an instance of HttpClient and then invoke PostAsync on it passing as parameter the HttpContent.
I think this is the best approach so could someone tell me the necessary steps I need to do in order to make the same things I do in DumpWarehouseDataIntoFile method but using IHttpClientFactory? I am a bit lost, I do not know how to apply IHttpClientFactory to do the same as I do within DumpWarehouseDataIntoFile method.
Any others solutions not proposed here and also some code snippets will be highly appreciated.
HttpClient
The HttpClient can throw InvalidOperationException in the following cases:
When the BaseAddress setter is called after a request has been sent out
When the Timeout setter is called after a request has been sent out
When the MaxResponseContentBufferSize setter is called after a request has been sent out
When an operation has already started and resend was requested
In order to avoid these you can set the first two on per request level, for example:
CancellationTokenSource timeoutSource = new CancellationTokenSource(2000);
await httpClient.GetAsync("http://www.foo.bar", timeoutSource.Token);
HttpClientFactory
You can use the IHttpClientFactory in .NET Framework with the following trick:
AddHttpClient registers the DefaultHttpClientFactory for IHttpClientFactory
Then you can retrieve it from the DI container
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
container.RegisterInstance(serviceProvider.GetService<IHttpClientFactory>());
container.ContainerScope.RegisterForDisposal(serviceProvider);
This sample uses SimpleInjector but the same concept can be applied for any other DI framework.
I'm not sure but will what happen if you move this lines to constructor:
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
I think that re-initialization is problem.
Better to add the request url and the headers at the message. Don't use httpClient.BaseAddress or httpClient.DefaultRequestHeaders unless you have a default requirement.
HttpRequestMessage msg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(msg);
It works well for reusing the HttpClient for many requests
I have a simple API gateway controller which returns an IActionResult. The issue is I am not able to read the body of the response.
If I comment out the using block in ExecuteResultAsync it seems to work fine but there is not content/body.
Not sure how to get this working with the httpbody being returned. RouteRequest returning HttpResponseMessage is not an option as it puts the response from the microservice as the body of the response from the Gateway.
So I need to use the HttpResponseMessageResult middleware, which works as expected for headers but not for the body.
public async Task<IActionResult> RouteRequest()
{
// Calls a method which send a request and gets a response and constructs a HttpResponseMessage
_contextAccessor.HttpContext.Response.RegisterForDispose(response);
return new HttpResponseMessageResult(response);
}
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode;
var responseMessageHeadersArray = _responseMessage.Headers.ToArray();
for (int i = 0; i < responseMessageHeadersArray.Length; i++)
{
var header = responseMessageHeadersArray[i];
context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray()));
}
using (var stream = await _responseMessage.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(context.HttpContext.Response.Body);
await context.HttpContext.Response.Body.FlushAsync();
}
}
}
Try this out, based on this good answer to a similar question, I used the ObjectResult class instead of manually manipulating the streams. When I run it with response from one of our API's (JSON), I get the same amount of data in the body of objectResult when it calls ExecuteAsync as were in the initial response.
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(await _responseMessage.Content.ReadAsStreamAsync())
{StatusCode = (int)_responseMessage.StatusCode};
foreach (KeyValuePair<string, IEnumerable<string>> h in _responseMessage.Headers)
{
context.HttpContext.Response.Headers.TryAdd(h.Key, string.Join("", h.Value));
}
await objectResult.ExecuteResultAsync(context);
}
}
I am trying to get data from REST API using HttpClient, but I have an issue.
Using the same service but from Console Application, everything works fine.
From Controller everything works fine, but when GetAsync(url) method from HttpHandler is calling, it looks like something works in the background but nothing happen..
This is my service:
public class UserService : IUsersService
{
private const string url = "https://jsonplaceholder.typicode.com/users";
private IHttpHandler httpHandler;
public UserService(IHttpHandler httpHandler)
{
this.httpHandler = httpHandler;
}
public List<User> GetAllUsers()
{
HttpResponseMessage response = httpHandler.Get(url);
if (response.IsSuccessStatusCode)
{
return response.Content.ReadAsAsync<List<User>>().Result;
}
//Nice to add Logging system that we cannot connect into following URL
return new List<User>();
}
public User GetUserById(int userId)
{
HttpResponseMessage response = httpHandler.Get(
string.Concat(url,"?id=",userId));
if (response.IsSuccessStatusCode)
{
return response.Content.ReadAsAsync<List<User>>().Result.FirstOrDefault();
}
//Nice to add Logging system that we cannot connect into following URL
return null;
}
}
This is my Controller (using WEB API controller, httpClient is not getting data from REST API)
public class UsersController : ApiController
{
IUsersService userService;
public UsersController(IUsersService userService)
{
this.userService = userService;
}
public List<User> GetUsers()
{
return userService.GetAllUsers();
}
public User GetUser(int userId)
{
return userService.GetUserById(userId);
}
}
And this is my HttpHandler which is currently using HttpClient:
public class HttpHandler : IHttpHandler
{
private HttpClient client = new HttpClient();
public HttpResponseMessage Get(string url)
{
return GetAsync(url).Result;
}
public HttpResponseMessage Post(string url, HttpContent content)
{
return PostAsync(url, content).Result;
}
public async Task<HttpResponseMessage> GetAsync(string url)
{
return await client.GetAsync(url);
}
public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
{
return await client.PostAsync(url, content);
}
}
This is my console Application which is working well and shows correct result:
class Program
{
static void Main(string[] args)
{
HttpHandler handler = new HttpHandler();
UserService service = new UserService(handler);
var users = service.GetAllUsers();
Console.WriteLine(users[0].Email);
Console.ReadKey();
}
}
I don't really know, what could be a problem.
During digging network, I found the solution to my problem https://stackoverflow.com/a/10369275/5002910
In the HttpHandler class in the GetAsync method I have to return
return await client.GetAsync(url).ConfigureAwait(continueOnCapturedContext:false);
I am using this test method (and helper class) to verify the response from an external web service:
[TestMethod]
public void WebServiceReturnsSuccessResponse()
{
using (var provider = new Provider(new Info()))
using (var result = provider.GetHttpResponseMessage())
{
Assert.IsTrue(result.IsSuccessStatusCode);
}
}
private class Info : IInfo
{
public string URL { get; set; } =
"https://notreallythe.website.com:99/service/";
public string User { get; set; } = "somename";
public string Password { get; set; } = "password1";
}
I can't get this test to pass; I always get a 500 - Internal Server Error result. I have connected via an external utility (Postman) - so the web service is up and I can connect with the url & credentials that I have.
I think the problem is in my instantiation of the HttpClient class, but I can't determine where. I am using Basic authentication:
public class Provider : IProvider, IDisposable
{
private readonly HttpClient _httpClient;
public Provider(IInfo config){
if (config == null)
throw new ArgumentNullException(nameof(config));
var userInfo = new UTF8Encoding().GetBytes($"{config.User}:{config.Password}");
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.URL),
DefaultRequestHeaders =
{
Accept = { new MediaTypeWithQualityHeaderValue("application/xml")},
Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(userInfo)),
ExpectContinue = false,
},
};
}
public HttpResponseMessage GetHttpResponseMessage()
{
return _httpClient.GetAsync("1234").Result;
}
}
The response I get back appears to go to the correct endpoint; the RequestUri in the response looks exactly like I expect, https://notreallythe.website.com:99/service/1234.
You need to load up Fiddler and do a recording of the HTTP traffic when this operation succeeds (through the browser).
Then, load up your code, stand up another instance (or window) of Fiddler, and do the same thing with your code. Now, compare the two Fiddler windows to see what is different.
You only need to compare those things in Fiddler that are highlighted in blue. You can ignore the other communications.
I am posting an object to a WebApi method. I'm using PostAsJsonAsync to do this.
public async Task<HttpResponseMessage> PostAsync(string token, ServiceCall call)
{
var client = new HttpClient();
client.SetBearerToken(token);
var response = await client.PostAsJsonAsync(Uri + "id/nestedcall", call);
return response;
}
The object call that I'm passing is not null when I post it.
[HttpPost]
[Route("id/nestedcall")]
public async Task<IHttpActionResult> NestedCall([FromBody]ServiceCall call)
{
// call is null here
}
However it is null in my API method. I can't seem to work out why as all of the examples I've followed use this format.
Why isn't the call object being picked up by the web api?
Edit
Here is the ServiceCall object. It is in a separate class library and a reference is included in both the web application and the API.
public class ServiceCall
{
public ServiceCall(Service service, string grantType)
{
ClientId = service.Id;
ClientSecret = service.Secret;
Uri = service.Uri;
Scope = service.Scope;
GrantType = grantType;
}
public ServiceCall(string clientid, string clientsecret, string uri, string scope, string grantType)
{
ClientId = clientid;
ClientSecret = clientsecret;
Uri = uri;
Scope = scope;
GrantType = grantType;
}
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string Uri { get; set; }
public string Scope { get; set; }
public string GrantType { get; set; }
}
I have seen Object null in WebApi method after PostAsJsonAsync due to serialization.
Better to use PostAsync like below :
var obj = new MyClass()
{
MyProperty = 11
};
using (var client = new HttpClient())
{
string inputJson = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
HttpContent inputContent = new StringContent(inputJson, Encoding.UTF8, "application/json");
HttpResponseMessage response1 = client.PostAsync("http://localhost:60909/api/home/Test", inputContent).Result;
if (response1.IsSuccessStatusCode)
{
}
}
Using Prefix Stackify I was able to diagnose that the serialiser was throwing an exception:
Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type Core.Models.ServiceCall. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'ClientId', line 1, position 12.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize
However, very helpfully, rather than tell me that an exception occurred the controller simply gave me a null object.
As hinted by the exception the solution is to add a default constructor (or at least one the serialiser can understand).
public ServiceCall()
{
}
looks like the JSON serialization may be failing. BTW, remove that [FromBody] and try without it like below. PostAsJsonAsync method serializes the ServiceCall object to JSON and then sends the JSON payload in a POST request.
public async Task<IHttpActionResult> NestedCall(ServiceCall call)
{
// your code
}
I run into exactly the same problem and had to do this to solve it:
using (var client = new HttpClient())
{
client.SetBearerToken(token);
var content = new StringContent(JsonConvert.SerializeObject(call), Encoding.UTF8, "application/json");
var response = await client.PostAsJsonAsync(Uri + "id/nestedcall", content);
return response;
}