I have a WindowsForm application and i want to send an List<> to the Web API
here is my code in the windows form app:
Uri uri = new Uri("http://localhost/test/api/v1/name/testcontroller/");
HttpClient client = new HttpClient();
client.BaseAddress = uri;
var mediaType = new MediaTypeHeaderValue("application/json");
var jsonFormatter = new JsonMediaTypeFormatter();
HttpContent content = new ObjectContent<List<TermbaseFile>>(termbaseList, jsonFormatter);
HttpResponseMessage responseMessage = client.PostAsync(uri, content).Result;
What should i put in the controller-method to get the List?
You need to implement a Post action that expects a list of that particular object type, or more specifically, an object which has the same properties e.g.
public class TermbaseFilePostDto
{
// relevant properties go here
}
public class TestController : ApiController
{
public HttpResponseMessage Post(List<TermbaseFileDto> list)
{
...
}
}
Related
I am coding both a client and an API in C# .Net4.8. I am POSTing data from the client and I have an ActionFilterAttribute on the endpoint method. I want to read the POSTed data within the ActionFilterAttribute method. I found I was able to POST form data using FormUrlEncodedContent and it is received, but when I try POSTing JSON data using stringContent it is not received.
How can I change either my client side code or API code to POST JSON correctly?
POSTing form data like so works:
HttpClientHandler handler = new HttpClientHandler()
HttpClient httpClient = new HttpClient(handler);
FormUrlEncodedContent formString = new FormUrlEncodedContent(data);
response = httpClient.PostAsync(url, formString).Result; // run synchronously
And then on the API side, dataFromClient gets populated:
public class myFilter : ActionFilterAttribute
{
public string Feature { get; set; }
public myFilter(string feature)
{
this.Feature = feature;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string dataFromClient = (HttpContext.Current.Request.Params["dataFromClient"] == null) ? "" : HttpContext.Current.Request.Params["dataFromClient"];
// do other stuff with dataFromClient here
}
}
POSTing JSON data like so does not work:
HttpClientHandler handler = new HttpClientHandler()
HttpClient httpClient = new HttpClient(handler);
StringContent stringContent = new StringContent(jsonString, System.Text.Encoding.UTF8, "application/json");
response = httpClient.PostAsync(url, stringContent).Result; // run synchronously
With this method, dataFromClient in the API is empty.
Since you're posting application/json,you should read request body. I think, here's what you want;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
var initialBody = request.Body;
try
{
request.EnableRewind();
using (StreamReader reader = new StreamReader(request.Body))
{
string dataFromClient = reader.ReadToEnd();
// do other stuff with dataFromClient here
return dataFromClient;
}
}
finally
{
request.Body = initialBody;
}
filterContext.Request.Body.Position = 0
return string.Empty;
}
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 want to send an image with C# HttpClient, receive it in ASP.NET Core controller and save it to disk. I tried various methods but all i'm getting in controller is null reference.
My http client:
public class HttpClientAdapter
{
private readonly HttpClient _client;
public HttpClientAdapter()
{
_client = new HttpClient();
}
public async Task<HttpResponse> PostFileAsync(string url, string filePath)
{
var requestContent = ConstructRequestContent(filePath);
var response = await _client.PostAsync(url, requestContent);
var responseBody = await response.Content.ReadAsStringAsync();
return new HttpResponse
{
StatusCode = response.StatusCode,
Body = JsonConvert.DeserializeObject<JObject>(responseBody)
};
}
private MultipartFormDataContent ConstructRequestContent(string filePath)
{
var content = new MultipartFormDataContent();
var fileStream = File.OpenRead(filePath);
var streamContent = new StreamContent(fileStream);
var imageContent = new ByteArrayContent(streamContent.ReadAsByteArrayAsync().Result);
imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
content.Add(imageContent, "image", Path.GetFileName(filePath));
return content;
}
}
and controller:
[Route("api/files")]
public class FilesController: Controller
{
private readonly ILogger<FilesController> _logger;
public FilesController(ILogger<FilesController> logger)
{
_logger = logger;
}
[HttpPost]
public IActionResult Post(IFormFile file)
{
_logger.LogInformation(file.ToString());
return Ok();
}
}
As i mentioned above, the IFormFile object i'm getting in the controller is null reference. I tried adding [FromBody], [FromForm], tried creating class with two properties: one of type string and one with type IFormFile, but nothing works. Also instead of sending file with C# HttpClient i used Postman - same thing happens.
Does anyone know solution for this problem? Thanks in advance.
The name of the form field must match the property name:
content.Add(imageContent, "file", Path.GetFileName(filePath));
file instead of image, since you use file in
public IActionResult Post(IFormFile file)
{
}
I am trying to create a basic test web api, and use a standard controller to test call it.
When I run it, by putting
http://localhost:55144/home/testapi
it'll run the catcher function and completely ignore the parameter.
Then, the catcher will happily return a value, which can be seen in the calling code.
I have tried various combinations of putting [FromBody], changing the type of the parameter in TestApiMethod, and seeing if making a list or array makes any difference.
I've noticed a couple of weird things:
- I'm not using the parameter in the code of TestApiMethod, but Visual Studio is not giving me an unused variable warning.
- If I make the type of the parameter testString a string or even an int, the code below will route to the catcher. If I make it some variation of a model or a Jobject, it will not. It gets as far as running
HttpResponseMessage response = await client.PostAsJsonAsync("api/activity", sendData);
then just returns to the web page.
Here's the code:
Models
public class testStringModel
{
public string testString { get; set; }
}
public class apiResponse
{
public string response { get; set; }
}
Home controller calling Api:
public void TestApi()
{
Task myTask = testApiCall();
}
private async Task<string> testApiCall()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:55144");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
testStringModel data = new testStringModel { testString = "cheese" };
string jsonData = JsonConvert.SerializeObject(data);
var sendData = new StringContent(jsonData, Encoding.UTF8, "application/json");
//var sendData = new Dictionary<string, string>
//{
// {"testString", "cheese"}
//};
HttpResponseMessage response = await client.PostAsJsonAsync("api/activity", sendData);
string responseBodyAsText = await response.Content.ReadAsStringAsync();
dynamic stuff = JObject.Parse(responseBodyAsText);
string finalResponse = stuff.response;
return finalResponse;
}
}
The api:
namespace ApplicationActivity
{
public class ActivityController : ApiController
{
[System.Web.Http.HttpPost]
public HttpResponseMessage Catcher()
{
apiResponse apiResponseObject = new apiResponse();
apiResponseObject.response = "You have somehow wound up in the catcher";
string json = JsonConvert.SerializeObject(apiResponseObject);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent(json, Encoding.Unicode, "application/json");
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
[System.Web.Http.HttpPost]
public HttpResponseMessage TestApiMethod(string testString)
{
apiResponse apiResponseObject = new apiResponse();
apiResponseObject.response = "OK from test";
string json = JsonConvert.SerializeObject(apiResponseObject);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent(json, Encoding.Unicode, "application/json");
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
}
}
Please will you tell me what I'm doing wrong with my code, how to fix it and what is happening when the code doesn't get to the catcher?
Thanks.
It turns out that I was using an older version of visual studio and as a result the whole thing got really confused with whether is was running .net core or not.
Upgrading to the latest and making sure the latest .net core is installed solved most of my troubles
seems like I have the most basic problem, yet I cannot find the documentation I need to solve it.
I have an mvc webapi controller:
public class TestController : ApiController
{
[HttpGet]
public MyClass Other([FromUri]MyClass id)
{
id.Value++;
return id;
}
}
public class MyClass
{
public int Value {get;set;}
}
which I am executing from a HttpClient:
using (var client = new System.Net.Http.HttpClient())
{
client.BaseAddress = new System.Uri("http://localhost:31573/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var obj = new MyClass { Value = 3 };
var data = JsonConvert.SerializeObject(obj);
StringContent queryString = new StringContent(data, Encoding.UTF8, "application/json");
var paramsValue = queryString.ReadAsStringAsync().Result;
var response = client.GetAsync("Test/?id="+ paramsValue).Result;
var textResponse = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<MyClass>(textResponse);
}
The problem is that the parameter id which is received by the controller is a default instance of MyClass (i.e. Value = 0). If I change the prototype of the method to accept a string:
[HttpGet]
public MyClass Other([FromUri]string id)
{
var val = JsonConvert.DeserializeObject<MyClass>(id);
val.Value++;
return val;
}
it all works fine, but I would rather not have to manually do the deserialization in every controller method.
I have tried many combinations of how I create the query string, so far having no luck.
It appears that the data is getting to the webApi correctly, but the deserialization is not happening, so I suspect that I have not configure the webApi correctly to use json form the request parameters.
my webapiconfig looks like:
public static void Register(HttpConfiguration config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
// Web API configuration and services
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
so its returning json correctly, but incoming parameters are not deserialized correctly.
Can anyone help??
Thanks to the help from #Mostafizur Rahman, I came to the conclusion that a get method was not appropriate here, So I have changed to a Post method and pass the data in the body:
public class TestController : ApiController
{
public MyClass PostMethod([FromBody]MyClass id)
{
id.Value++;
return id;
}
}
public class MyClass
{
public int Value {get;set;}
}
with the client side becoming:
using (var client = new System.Net.Http.HttpClient())
{
client.BaseAddress = new System.Uri("http://localhost:31573/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var obj = new MyClass { Value = 3 };
var data = JsonConvert.SerializeObject(obj);
StringContent queryString = new StringContent(data, Encoding.UTF8, "application/json");
var paramsValue = queryString.ReadAsStringAsync().Result;
var response = client.PostAsync("Test/PostMethod", queryString).Result;
var textResponse = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<MyClass>(textResponse);
}
this is the querystring that you'll need to form -
http://localhost:61817/api/Test?Value=3
Then the code changes like -
var response = client.GetAsync("Test?Value="+ obj.Value).Result;