Httpclient consume web api via console app C# - c#

I am trying to consume the below web api via console app using Httpclient. I am stuck as in how to pass the parameter. The paramter is coming as a command line argument.
This is my Rest api
[HttpPost, Route("Test")]
public IHttpActionResult Test(bool sample = false)
{
return Ok();
}
The parameter comes in this was as command line argument
/Parameters:sample=true.
Here is how I am parsing out the parameter in the console app
static int Main(string[] args)
{
if (args.Length == 0)
{
Console.Error.WriteLine("No action provided.");
return -1;
}
foreach (string param in args)
{
switch (param.Substring(0, param.IndexOf(":")))
{
case "/Action":
action = param.Substring(param.IndexOf(":") + 1);
break;
case "/Parameters":
parameter = param.Substring(param.IndexOf(":") + 1);
break;
}
}
return 0;
}
Once I get my parameter which is in this format
parameter = "sample=true"
I am trying to invoke the web api call but unable to pass the parameter value. can anybody pin point what I am doing wrong
client.BaseAddress = new Uri(ConfigurationManager.AppSettings.Get("BaseWebApiUrl"));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var apiUrl = GetApiURL();
var Context = new StringContent(JsonConvert.SerializeObject(parameter), Encoding.UTF8, "application/json");
var respFeed = await client.PostAsync(apiUrl, Context);

By default in Web Api basic type parameters are never bound to the body of the request.
If you want to forcefully bind your bool parameter with the request body you need to decorate it with FromBodyAttribute:
[HttpPost, Route("Test")]
public IHttpActionResult Test([FromBody] bool sample = false)
{
return Ok();
}
Be aware that even if you do this your request is not valid for Web Api. A single basic type parameter must be passed with a specific format. In your case your request body must be the following:
=true
A better approach is to turn your Action parameter into a class:
public class TestModel
{
public bool Sample { get; set; }
}
[HttpPost, Route("Test")]
public IHttpActionResult Test(TestModel model)
{
return Ok();
}
This way you will be able to send a Json object as request body, like:
{
"sample": true
}
This is a small example of how you could achieve such a result:
var parameterDictionary = parameter.Split("=").ToDictionary(s => s[0], s => bool.Parse(s[1]));
var json = JsonConvert.SerializeObject(parameterDictionary);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var respFeed = await client.PostAsync(apiUrl, content);

You should pass it as a part of the url.
private void CallService(bool sample)
{
client.BaseAddress = new Uri(ConfigurationManager.AppSettings.Get("BaseWebApiUrl"));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var apiUrl = $"http://yourserver:port/api/somecontroller/?sample={sample}";
var Context = new StringContent(JsonConvert.SerializeObject(parameter), Encoding.UTF8, "application/json");
var respFeed = await client.PostAsync(apiUrl, Context);
}

First, change your method signature to
public IHttpActionResult Test([FromBody]bool sample = false)
since primitive types are resolved from URL/Query String and not body. By adding FromBody attribute will force it to resolve from the body.
Next, since you said you have parameter value as var parameter = "sample=true";, change your parameter construction to
var jsonParam = new { sample = parameter.split('=')[1]}; // I'm leaving null checks and validation for simplicity
var Context = new StringContent(JsonConvert.SerializeObject(jsonParam), Encoding.UTF8, "application/json");

Related

Send Http Request from Blazor Component

I'm using .Net Core 3.1 and I'm having trouble sending requests from a Blazor component. I want to send a request to a controller I have, and these requests systematically end up in 400 Bad request.
In my Startup.cs, I have
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
services.AddScoped<HttpClient>(s =>
{
var uriHelper = s.GetRequiredService<NavigationManager>();
return new HttpClient
{
BaseAddress = new Uri(uriHelper.BaseUri)
};
});
}
In my Blazor component, I have:
var json2 = Newtonsoft.Json.JsonConvert.SerializeObject(_Model);
var stringContent2 = new StringContent(json2, System.Text.Encoding.UTF8, "application/json");
var response2 = await Http.PostAsync("/[controllerName]/[Method]", stringContent2);
if (response2.IsSuccessStatusCode)
{
var resultContent = response2.Content.ReadAsStringAsync().Result;
return resultContent;
}
else
return "failed";
And here is my Controller Method prototype:
[HttpPost]
public IActionResult Method([FromBody] Model form)
{...}
Would you happen to see what's wrong with the code?
You are passing a StringContent object in your PostAsync method, but in your action you have your Model as a parameter.
You have two options :
To change your action parameter to a StringContent.
To parse the Json as your Model to pass it to the PostAsync method content parameter.
Regards,
these requests systematically end up in 400 Bad request.
Please check you provide correct request header(s) and well-formatted data while you make request from your Blazor app to backend service.
I did a test using following code snippet with simple testing data, which work well on my side. If possible, you can create a new component and test if the code snippet can work for you.
var _Model = new Model { Id = 1, Name = "fehan" };
var json2 = Newtonsoft.Json.JsonConvert.SerializeObject(_Model);
var stringContent2 = new StringContent(json2, System.Text.Encoding.UTF8, "application/json");
var response2 = await Http.PostAsync("Home/Method", stringContent2);
if (response2.IsSuccessStatusCode)
{
var resultContent = response2.Content.ReadAsStringAsync().Result;
}
Model class
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
Test Result
Besides, if you are making request to MVC controller action, please check if you enabled antiforgery validation on controller or action(s).

WebApi - why is my post variable always null?

I'd like to be able to read a post variable from my controller method.
I currently have the below code:
[HttpPost]
public IHttpActionResult BuildPartitions([FromBody]string PartitionBuildDate)
{
}
I'm using the below code to test:
using (HttpClient httpClient = new HttpClient())
{
var values = new Dictionary<string, string>
{
{ "PartitionBuildDate", "24-May-2017" }
};
var content = new FormUrlEncodedContent(values);
var response = httpClient.PostAsync("http://localhost:55974/api/Controller/BuildPartitions", content);
var responseString = response.Result.Content;
}
Looking online, this looks correct for both sending and receiving a post variable in C#, however the PartitionBuildDate variable is always null.
Try adding the content-type header. I have used Newtonsoft JSON.NET for JSON conversion:
string postBody = JsonConvert.SerializeObject(yourDictionary);
var response = client.PostAsync(url, new StringContent(postBody, Encoding.UTF8, "application/json"));
var responseString = response.Result.Content;
Also, on your web API side, try wrapping your POST parameters inside a class:
public class PostParameters
{
public string PartitionBuildDate {get;set;}
}
[HttpPost]
public IHttpActionResult BuildPartitions([FromBody]PostParameters parameters)
{
//you can access parameters.PartitionBuildDate
}

WebAPi - unify error messages format from ApiController and OAuthAuthorizationServerProvider

In my WebAPI project I'm using Owin.Security.OAuth to add JWT authentication.
Inside GrantResourceOwnerCredentials of my OAuthProvider I'm setting errors using below line:
context.SetError("invalid_grant", "Account locked.");
this is returned to client as:
{
"error": "invalid_grant",
"error_description": "Account locked."
}
after user gets authenticated and he tries to do "normal" request to one of my controllers he gets below response when model is invalid (using FluentValidation):
{
"message": "The request is invalid.",
"modelState": {
"client.Email": [
"Email is not valid."
],
"client.Password": [
"Password is required."
]
}
}
Both requests are returning 400 Bad Request, but sometimes You must look for error_description field and sometimes for message
I was able to create custom response message, but this only applies to results I'm returning.
My question is: is it possible to replace message with error in response that is returned by ModelValidatorProviders and in other places?
I've read about ExceptionFilterAttribute but I don't know if this is a good place to start. FluentValidation shouldn't be a problem, because all it does is adding errors to ModelState.
EDIT:
Next thing I'm trying to fix is inconsistent naming convention in returned data across WebApi - when returning error from OAuthProvider we have error_details, but when returning BadRequest with ModelState (from ApiController) we have modelState. As You can see first uses snake_case and second camelCase.
UPDATED ANSWER (Use Middleware)
Since the Web API original delegating handler idea meant that it would not be early enough in the pipeline as the OAuth middleware then a custom middleware needs to be created...
public static class ErrorMessageFormatter {
public static IAppBuilder UseCommonErrorResponse(this IAppBuilder app) {
app.Use<JsonErrorFormatter>();
return app;
}
public class JsonErrorFormatter : OwinMiddleware {
public JsonErrorFormatter(OwinMiddleware next)
: base(next) {
}
public override async Task Invoke(IOwinContext context) {
var owinRequest = context.Request;
var owinResponse = context.Response;
//buffer the response stream for later
var owinResponseStream = owinResponse.Body;
//buffer the response stream in order to intercept downstream writes
using (var responseBuffer = new MemoryStream()) {
//assign the buffer to the resonse body
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
//reset body
owinResponse.Body = owinResponseStream;
if (responseBuffer.CanSeek && responseBuffer.Length > 0 && responseBuffer.Position > 0) {
//reset buffer to read its content
responseBuffer.Seek(0, SeekOrigin.Begin);
}
if (!IsSuccessStatusCode(owinResponse.StatusCode) && responseBuffer.Length > 0) {
//NOTE: perform your own content negotiation if desired but for this, using JSON
var body = await CreateCommonApiResponse(owinResponse, responseBuffer);
var content = JsonConvert.SerializeObject(body);
var mediaType = MediaTypeHeaderValue.Parse(owinResponse.ContentType);
using (var customResponseBody = new StringContent(content, Encoding.UTF8, mediaType.MediaType)) {
var customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream, (int)customResponseStream.Length, owinRequest.CallCancelled);
owinResponse.ContentLength = customResponseStream.Length;
}
} else {
//copy buffer to response stream this will push it down to client
await responseBuffer.CopyToAsync(owinResponseStream, (int)responseBuffer.Length, owinRequest.CallCancelled);
owinResponse.ContentLength = responseBuffer.Length;
}
}
}
async Task<object> CreateCommonApiResponse(IOwinResponse response, Stream stream) {
var json = await new StreamReader(stream).ReadToEndAsync();
var statusCode = ((HttpStatusCode)response.StatusCode).ToString();
var responseReason = response.ReasonPhrase ?? statusCode;
//Is this a HttpError
var httpError = JsonConvert.DeserializeObject<HttpError>(json);
if (httpError != null) {
return new {
error = httpError.Message ?? responseReason,
error_description = (object)httpError.MessageDetail
?? (object)httpError.ModelState
?? (object)httpError.ExceptionMessage
};
}
//Is this an OAuth Error
var oAuthError = Newtonsoft.Json.Linq.JObject.Parse(json);
if (oAuthError["error"] != null && oAuthError["error_description"] != null) {
dynamic obj = oAuthError;
return new {
error = (string)obj.error,
error_description = (object)obj.error_description
};
}
//Is this some other unknown error (Just wrap in common model)
var error = JsonConvert.DeserializeObject(json);
return new {
error = responseReason,
error_description = error
};
}
bool IsSuccessStatusCode(int statusCode) {
return statusCode >= 200 && statusCode <= 299;
}
}
}
...and registered early in the pipeline before the the authentication middlewares and web api handlers are added.
public class Startup {
public void Configuration(IAppBuilder app) {
app.UseResponseEncrypterMiddleware();
app.UseRequestLogger();
//...(after logging middle ware)
app.UseCommonErrorResponse();
//... (before auth middle ware)
//...code removed for brevity
}
}
This example is just a basic start. It should be simple enough able to extend this starting point.
Though in this example the common model looks like what is returned from OAuthProvider, any common object model can be used.
Tested it with a few In-memory Unit Tests and through TDD was able to get it working.
[TestClass]
public class UnifiedErrorMessageTests {
[TestMethod]
public async Task _OWIN_Response_Should_Pass_When_Ok() {
//Arrange
var message = "\"Hello World\"";
var expectedResponse = "\"I am working\"";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(message, Encoding.UTF8, "application/json");
//Act
var response = await client.PostAsync("/api/Foo", content);
//Assert
Assert.IsTrue(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_BadRequest() {
//Arrange
var expectedResponse = "invalid_grant";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(expectedResponse, Encoding.UTF8, "application/json");
//Act
var response = await client.PostAsync("/api/Foo", content);
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error_description);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_MethodNotAllowed() {
//Arrange
var expectedResponse = "Method Not Allowed";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Act
var response = await client.GetAsync("/api/Foo");
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_NotFound() {
//Arrange
var expectedResponse = "Not Found";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Act
var response = await client.GetAsync("/api/Bar");
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error);
}
}
public class WebApiTestStartup {
public void Configuration(IAppBuilder app) {
app.UseCommonErrorMessageMiddleware();
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
app.UseWebApi(config);
}
}
public class FooController : ApiController {
public FooController() {
}
[HttpPost]
public IHttpActionResult Bar([FromBody]string input) {
if (input == "Hello World")
return Ok("I am working");
return BadRequest("invalid_grant");
}
}
}
ORIGINAL ANSWER (Use DelegatingHandler)
Consider using a DelegatingHandler
Quoting from an article found online.
Delegating handlers are extremely useful for cross cutting concerns.
They hook into the very early and very late stages of the
request-response pipeline making them ideal for manipulating the
response right before it is sent back to the client.
This example is a simplified attempt at the unified error message for HttpError responses
public class HttpErrorHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var response = await base.SendAsync(request, cancellationToken);
return NormalizeResponse(request, response);
}
private HttpResponseMessage NormalizeResponse(HttpRequestMessage request, HttpResponseMessage response) {
object content;
if (!response.IsSuccessStatusCode && response.TryGetContentValue(out content)) {
var error = content as HttpError;
if (error != null) {
var unifiedModel = new {
error = error.Message,
error_description = (object)error.MessageDetail ?? error.ModelState
};
var newResponse = request.CreateResponse(response.StatusCode, unifiedModel);
foreach (var header in response.Headers) {
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
return response;
}
}
Though this example is very basic, it is trivial to extend it to suit your custom needs.
Now it is just a matter of adding the handler to the pipeline
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MessageHandlers.Add(new HttpErrorHandler());
// Other code not shown...
}
}
Message handlers are called in the same order that they appear in
MessageHandlers collection. Because they are nested, the response message travels in the other direction. That is, the last handler is
the first to get the response message.
Source: HTTP Message Handlers in ASP.NET Web API
is it possible to replace message with error in response that is
returned by ModelValidatorProviders
We may use overloaded SetError to do it otherwise, replace error with message.
BaseValidatingContext<TOptions>.SetError Method (String)
Marks this context as not validated by the application and assigns various error information properties. HasError becomes true and IsValidated becomes false as a result of calling.
string msg = "{\"message\": \"Account locked.\"}";
context.SetError(msg);
Response.StatusCode = 400;
context.Response.Write(msg);

POSTing to WebApi2 from C#

I can't for the life of me get this to work. I keep getting 404.
Here's the WebApi2 code:
[HttpPost]
public IHttpActionResult Post(string testString)
{
if(!string.IsNullOrEmpty(testString))
{
return Ok(testString);
}
else
{
return BadRequest();
}
}
Here's the WebClient code:
public async Task PostingToWebServiceShouldWork()
{
var apiEndPoint = new Uri(String.Format("{0}/Paging", ConfigurationManager.AppSettings["ApiEndpoint"].ToString()));
var apiRoot = new Uri(apiEndPoint.GetLeftPart(UriPartial.Authority));
var apiCall = apiEndPoint.PathAndQuery.Substring(1, apiEndPoint.PathAndQuery.Length - 1);
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
client.BaseAddress = apiRoot;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpContent content = new StringContent("testSTring");
HttpResponseMessage response = await client.PostAsync(apiCall, content);
if(response.IsSuccessStatusCode)
{
}
}
}
I just want to post a simple string to the web service. This should be dead simple, and it's giving me a migraine, lol. I've tried everything I can think of, and I have to be missing some tiny detail...
Thanks!
Because your API endpoint is simply a string instead of an object, WebAPI is looking for that string as a query string parameter. You have two options:
Use the [FromBody] attribute in your action's definition
public IHttpActionResult Post([FromBody] string testString)
Send the string on the URL instead of in the body (works, but if you're going for security over HTTPS this exposes what you were posting)
See http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api for a deeper explanation and examples

Return Statuscode Unauthorized from Generic type method

I have created web api and calling service method from MVC application. I have implemented authentication in Web Api.
I have created Generic type method from which I call my service method and returning result to action method like below:
public async Task < T > GetWSObject < T > (string uriActionString)
{
T returnValue = default (T);
try
{
using(var client = new HttpClient())
{
//client.BaseAddress = new Uri("http://localhost:50524/");
//client.DefaultRequestHeaders.Accept.Clear();
//client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HttpClient cons = new HttpClient();
client.BaseAddress = new Uri("http://localhost:50524/"); // Web Service URL
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var data = Encoding.ASCII.GetBytes("Ankita:ankita12");
var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(data));
client.DefaultRequestHeaders.Authorization = header;
HttpResponseMessage response = await client.GetAsync(uriActionString);
response.EnsureSuccessStatusCode();
returnValue = JsonConvert.DeserializeObject < T > (((HttpResponseMessage) response).Content.ReadAsStringAsync().Result);
}
return returnValue;
}
catch (Exception e)
{
throw (e);
}
}
Moreover, I am calling this method from my action method like:
public async Task<ActionResult> ViewAuthor(Author author)
{
ViewBag.Message = "Your Author page.";
Author authors = new Author();
string urlAction = String.Format("api/Authors/GetAuthor/{0}", author.Id);
authors = await GetWSObject<Author>(urlAction);
return View(authors);
}
Now how can I return message from catch block in case of Unauthorized access as GetWSObject method is having generic type.
I just want to display message on my Author detail page like you are unauthorized to perform this action. How can I do it?
You have two choices
Add a type to wrap the result for example WSResult<T>. Then your method will return that type. It can look like this
public class WSResult
{
public T Result { get; set; }
public HttpStatusCode StatusCode { get; set; }
}
Then your method will return this type and consumers will check the status first. This is similar to how HttpResponseMessage works. You can copy ideas from it.
In this case your method has a signature like this
public async Task<WSResult<T>> GetWSObject<T> (string uriActionString)
You return things from it like this
return new WSResult<T> { Result = returnValue, Status = HttpStatusCode.OK }
Or in the catch
return new WSResult<T> { Result = returnValue, Status = HttpStatusCode.InternalServerError }
Note that the actual status code in the exception may differ and you should catch the proper exception. You can also use a boolean instead of HttpStatusCode and call it IsSuccessful or something. You can also roll your own enum with values suitable for your code.
Alternatively you can throw an exception that contains the status code. The second way is easier to implement and requires less refactoring but the first one is the recommended way.

Categories

Resources