HttpClient & WebAPI Unpredictable Redirect 302 - c#

I have a WPF app that uses HttpClient to call a Web API method on another server. Everything works fine, however, sometimes, especially when the client app wakes up (from power suspend), the call to the same server method returns Found: 302 instead of OK: 200.
The Web API method doesn't really do anything, and it just has an [Authorize] attribute to make sure the user is authenticated. Normally, if the user is not authenticated, it would return 404.
BTW, I'm not using ASP.NET forms authentication explicitly, but the redirect is sending the client to login.aspx, which doesn't exist.
Here's the code:
Web API Method (.NET 4.5):
[HttpGet]
public HttpStatusCodeResult IsAuthenticated()
{
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK);
}
WPF App client code (.NET 4.0):
public async Task<bool> IsAuthenticated()
{
try
{
Uri address = new Uri(AuthUrl);
var cookieJar = ReadCookiesFromDisk(COOKIE_FILE);
if (cookieJar.Count == 0)
return false;
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false,
AllowAutoRedirect = false
};
var client = new HttpClient(handler)
{
BaseAddress = address
};
int timeout = 15000;
var task = client.GetAsync(address.ToString());
Task[] tasks = new Task[1];
tasks[0] = task;
if (Task.WaitAny(tasks, timeout) != -1)
{
// task completed within timeout
// checking for redirect, but this should not happen!!!
HttpResponseMessage response = task.Result;
if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Redirect)
return true;
else
return false;
}
else
{
// timeout logic
return false;
}
}
catch (Exception e)
{
EventLogger.Log(e);
return false;
}
}
Client Authentication Code:
public async Task<string> Login(string data)
{
try
{
Uri address = new Uri(LoginUrl);
HttpContent content = new StringContent(data, Encoding.UTF8);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
var cookieJar = ReadCookiesFromDisk(COOKIE_FILE);
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false,
AllowAutoRedirect = false
};
var client = new HttpClient(handler)
{
BaseAddress = address
};
HttpResponseMessage response = await client.PostAsync(address.ToString(), content);
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
Uri uri = new Uri(UrlBase);
var responseCookies = cookieJar.GetCookies(uri);
if (responseCookies[".ASPXAUTH"] != null)
WriteCookiesToDisk(COOKIE_FILE, cookieJar);
return body;
}
catch (Exception e)
{
return e.ToString();
}
}
Server:
public JsonResult LogOn(string userInfo)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
LogOnModel model = serializer.Deserialize<LogOnModel>(userInfo);
JsonMessage error = null;
if (ModelState.IsValid)
{
try
{
if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
if (WebSecurity.IsConfirmed(model.UserName))
{
if (model.RememberMe)
Response.Cookies[0].Expires = DateTime.Now.AddDays(30);
else
Response.Cookies[0].Expires = DateTime.Now.AddDays(7);
var person = Dc.People.FirstOrDefault(x => x.UserName == model.UserName);
string fullName = string.Empty;
string email = string.Empty;
if (person != null)
{
fullName = string.Format("{0} {1}", person.FirstName, person.LastName);
email = person.Email;
//fill the session to create the session cookie
Session["1"] = 1;
}
var message = new { FailCount = 0, Message = WebSecurity.GetUserId(model.UserName).ToString(), Success = true, SuccessCount = 0, RedirectUrl = "#/conversations/priority", Name = fullName, UserEmail = email, UserHandle = model.UserName, UserAvatar = person.Avatar };
return Json(message);
}
else
{
error = new JsonMessage { FailCount = 0, Message = "Your aren't authorised to login", Success = false, SuccessCount = 0 };
TempData["Error"] = "Your are not authorised to login";
return Json(error);
}
}
else
{
TempData["Error"] = "The user name or password provided is incorrect.";
error = new JsonMessage { FailCount = 0, Message = "The user name or password provided is incorrect.", Success = false, SuccessCount = 0 };
return Json(error);
}
}
catch (Exception ex)
{
TempData["Error"] = ex.Message;
error = new JsonMessage { FailCount = 0, Message = ex.Message, Success = false, SuccessCount = 0 };
}
}
return Json(error);
}

Change following code
HttpResponseMessage response = await client.PostAsync(address.ToString(), content);
to
HttpResponseMessage response = await client.PostAsync(address.ToString(), content).ConfigureAwait(false);
above statement sometime show deadlock issues when using ASync. so when you configure await to false for Async Task, it will work in correct manner.

Related

Sending Multiple Files in a single Request to API

I am fairly new to API's. I am writing a "simple" API that will convert .docx files to .pdf files and return the pdf's back to the client for saving. I have the code working for a single file but I wanted to code the API to handle multiple files in a single request. Now the API is not receiving the request. I can provide the working code with a single file if requested.
I am sure I am missing something simple. Please see below and see if anyone see's something that I could be doing better or why the API is not receiving the POST request.
Client:
List<string> documents = new List<string>();
private async void btnConvert_Click(object sender, EventArgs e)
{
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
client.BaseAddress = new Uri(BaseApiUrl);
//client.DefaultRequestHeaders.Add("Accept", "application/json");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, BaseApiUrl + ApiUrl);
foreach (string s in docPaths)
{
byte[] bte;
bte = File.ReadAllBytes(docPath);
string data = JsonConvert.SerializeObject(Convert.ToBase64String(bte));
documents.Add(data);
}
using (var formData = new MultipartFormDataContent())
{
foreach (string s in documents)
{
//add content to form data
formData.Add(new StringContent(s, Encoding.UTF8, "application/json"));
}
// List of Http Reponse Messages
var conversions = documents.Select(doc => client.PostAsync(BaseApiUrl + ApiUrl, formData)).ToList();
//Wait for all the requests to finish
await Task.WhenAll(conversions);
//Get the responses
var responses = conversions.Select
(
task => task.Result
);
foreach (var r in responses)
{
// Extract the message body
var s = await r.Content.ReadAsStringAsync();
SimpleResponse res = JsonConvert.DeserializeObject<SimpleResponse>(s);
if (res.Success)
{
byte[] pdf = Convert.FromBase64String(res.Result.ToString());
// Save the PDF here
}
else
{
// Log issue
}
}
}
}
API: This is not getting the request so this function is not complete. I need to figure out why it not being hit.
[HttpPost]
public async Task<List<SimpleResponse>> Post([FromBody]string request)
{
var response = new List<SimpleResponse>();
Converter convert = new Converter();
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var requestContents in provider.Contents)
{
try
{
//var result = convert.CovertDocToPDF(requestContents, WebConfigurationManager.AppSettings["tempDocPath"], WebConfigurationManager.AppSettings["tempPdfPath"]);
//response.Add(new SimpleResponse() { Result = result, Success = true });
}
catch (Exception ex)
{
response.Add(new SimpleResponse() { Success = false, Exception = ex, Errors = new List<string>() { string.Format("{0}, {1}", ex.Message, ex.InnerException?.Message ?? "") } });
}
}
return response;
}
SimpleResponse Model:
public class SimpleResponse
{
public object Result { get; set; }
public bool Success { get; set; }
public Exception Exception { get; set; }
public List<string> Errors { get; set; }
}
UPDATE
Did suggestions made by #jdweng and I am getting a null response on the API POST
Client:
public async void btnConvert_Click(object sender, EventArgs e)
{
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
client.BaseAddress = new Uri(BaseApiUrl);
client.DefaultRequestHeaders.Add("Accept", "application/json");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//application/json
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, BaseApiUrl + ApiUrl);
List<string> requests = new List<string>();
byte[] bte;
// New Code per the suggestion
foreach (string s in docPaths)
{
bte = File.ReadAllBytes(s);
requests.Add(Convert.ToBase64String(bte));
}
// End new code
string data = JsonConvert.SerializeObject(requests);
request.Content = new StringContent(data, Encoding.UTF8, "application/json");
HttpResponseMessage response1 = client.PostAsync(BaseApiUrl + ApiUrl, request.Content).Result;
Task<string> json = response1.Content.ReadAsStringAsync();
SimpleResponse response = JsonConvert.DeserializeObject<SimpleResponse>(json.Result);
//result = JsonConvert.DeserializeObject(result).ToString();
if (response.Success)
{
bte = Convert.FromBase64String(response.Result.ToString());
if (File.Exists(tempPdfPath))
{
File.Delete(tempPdfPath);
}
System.IO.File.WriteAllBytes(tempPdfPath, bte);
}
else
{
}
}
}
Server:
[HttpPost]
public async Task<List<SimpleResponse>> Post([FromBody]string request)
{
// The request in NULL....
List<SimpleResponse> responses = new List<SimpleResponse>();
List<string> resp = JsonConvert.DeserializeObject(request) as List<string>;
try
{
Converter convert = new Converter();
foreach (string s in resp)
{
var result = convert.CovertDocToPDF(request, WebConfigurationManager.AppSettings["tempDocPath"], WebConfigurationManager.AppSettings["tempPdfPath"]);
responses.Add(new SimpleResponse()
{
Result = result,
Success = true
});
}
}
catch (Exception ex)
{
responses.Add(new SimpleResponse()
{
Result = null,
Success = true,
Exception = ex,
Errors = new List<string>() { string.Format("{0}, {1}", ex.Message, ex.InnerException?.Message ?? "") }
});
}
return responses;
}
I have finally solved the issues and can now successfully send multiple .docx files to the API and get the .pdf files back from the API. Now I want to figure out how to send each files on it's own thread instead of all together.
Client:
public async void btnConvert_Click(object sender, EventArgs e)
{
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
client.BaseAddress = new Uri(BaseApiUrl);
client.DefaultRequestHeaders.Add("Accept", "application/json");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, BaseApiUrl + ApiUrl);
List<string> requests = new List<string>();
byte[] bte;
foreach (string s in docPaths)
{
bte = File.ReadAllBytes(s);
requests.Add(Convert.ToBase64String(bte));
}
var data = JsonConvert.SerializeObject(requests);
request.Content = new StringContent(data, Encoding.UTF8, "application/json");
HttpResponseMessage response1 = await client.PostAsync(BaseApiUrl + ApiUrl, request.Content);
Task<string> json = response1.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<List<SimpleResponse>>(json.Result);
foreach (SimpleResponse sr in response)
{
if (sr.Success)
{
bte = Convert.FromBase64String(sr.Result.ToString());
string rs = RandomString(16, true);
string pdfFileName = tempPdfPath + rs + ".pdf";
if (File.Exists(pdfFileName))
{
File.Delete(pdfFileName);
}
System.IO.File.WriteAllBytes(pdfFileName, bte);
}
else
{
}
}
}
}
API:
[HttpPost]
public async Task<List<SimpleResponse>> Post([FromBody] List<string> request)
{
List<SimpleResponse> responses = new List<SimpleResponse>();
try
{
Converter convert = new Converter();
foreach (string s in request)
{
var result = convert.CovertDocToPDF(s, WebConfigurationManager.AppSettings["tempDocPath"], WebConfigurationManager.AppSettings["tempPdfPath"]);
responses.Add(new SimpleResponse()
{
Result = result,
Success = true
});
}
}
catch (Exception ex)
{
responses.Add(new SimpleResponse()
{
Result = null,
Success = true,
Exception = ex,
Errors = new List<string>() { string.Format("{0}, {1}", ex.Message, ex.InnerException?.Message ?? "") }
});
}
return responses;
}

How to prevent passing id and password in url for a get call in web api C#?

I have written a code to fetch details from active directory as a GET call based on user id and password. I am passing user id and password in the url like -http://localhost:1234/api/User/IsAuthorized/UserID=1234;password=qwerty
but this is an unsafe technique. Can anybody give me a solution to pass these values in the body and use it as a POST call instead of a get call
my code goes like-
[Route("IsAuthorized/UserID={userName};password={password}")]
[AllowAnonymous]
public IHttpActionResult GetIsAuthorized(string userName,string password)
{
HttpResponseMessage response = null;
string errorMessage = null;
bool hasError = false;
bool isValid;
UserDetails detail = null;
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "ABC"))
{
isValid = pc.ValidateCredentials(userName, password);
string token = null;
if (isValid)
{
detail = IsAuthenticated("abc", userName, password, out errorMessage, out hasError);
}
if (hasError)
{
detail = new UserDetails(isValid, userName, null, null, null, errorMessage, null);
}
else
{
if (detail != null)
{
token = CreateToken(userName);
detail = new UserDetails(isValid, userName, detail.AssociateName, detail.Mobile, detail.Email, null, token);
}
else
detail = new UserDetails(isValid, userName, null, null, null, "unknown username or bad password", null);
}
return Ok(detail);
}
}
Why are you even doing it like this? You must be calling you web api from a client. Why are you not using the HttpClient to pass your credentials to your api. Something like this:
public async Task<TResult> PostAsync<TResult, TInput>(string uriString, TInput payload = null) where TInput : class
{
var uri = new Uri(uriString);
using (var client = GetHttpClient())
{
var jsonContent = JsonConvert.SerializeObject(payload, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(jsonContent, Encoding.UTF8, "application/json"));
if (response.StatusCode != HttpStatusCode.OK)
{
//Log.Error(response.ReasonPhrase);
return default(TResult);
}
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResult>(json);
}
}
private HttpClient GetHttpClient()
{
var client = new HttpClient();
var username = //get username
var password = // get password
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}

asp.net core httpclient ArgumentNullException

I'm trying to make a POST request using HttpClient but it keeps throwing an ArgumentNullException.
on this call var response = await client.PostAsync(url, content);
I've set break points and verified that the values are populated.
Is there something I'm missing that would cause this?
public async Task<bool> PostSomeObject()
{
var transaction = new CreateTransactionRequest();
transaction.MerchantAuthentication.Name = "notmyName";
transaction.MerchantAuthentication.TransactionKey = "notMyKey";
transaction.TransactionRequest.TransactionType = "authOnlyTransaction";
transaction.TransactionRequest.Amount = 5;
transaction.TransactionRequest.Payment.CardCode = "999";
transaction.TransactionRequest.Payment.CardNumber = "5424000000000015";
transaction.TransactionRequest.Payment.ExpirationMonth = 12;
transaction.TransactionRequest.Payment.ExpirationYear = 20;
var url = "xml/v1/request.api";
using (HttpClient client = new HttpClient())
{
var sampleClassObjectJson = JsonConvert.SerializeObject(transaction);
client.BaseAddress = new Uri("http://localhost:56858/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(sampleClassObjectJson, Encoding.UTF8, "application/json");
//throws NullException on this call here
var response = await client.PostAsync(url, content);
if (response.StatusCode == HttpStatusCode.OK)
return true;
else
throw new WebException("An error has occurred while calling PostSomeObject method: " + response.Content);
}
}
Based on a comment I've added a try catch for the ArgumentNullException
try {
var response = await client.PostAsync(url, content);
}
catch (System.ArgumentNullException ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
It's not stepping into the catch statement but in the Output from debug it throws the exception.
Here is the controller action that calls my Post method
public async Task<IActionResult> PaymentInformation(Payment paymentInfo)
{
var cookie = GetCheckoutCookie();
//if there isn't a cookie ID something went wrong, redirect to Index
if (cookie.ID == null)
{
return RedirectToAction("Index");
}
var cookieSession = CheckoutSession.getCheckoutSession(cookie.ID);
if (ModelState.IsValid)
{
var status = await PostSomeObject();
}
else
{
}
return PartialView();
}

JSON, getting information using HttpPost

I'm recreating an old Cordova app in Xamarin forms PCL and I need to access this method that's on the server, providing it with the username and password and storing the information that comes back:
[HttpGet]
public JsonResult LoginUser(string userName, string password)
{
bool responseResult = false;
IEBUser user = null;
string errMsg = String.Empty;
try
{
if (String.IsNullOrEmpty(userName))
{
throw new Exception("userName is Empty");
}
else if (String.IsNullOrEmpty(password))
{
throw new Exception("userName is Password");
}
// connect to DB and find the user if it can
user = SelfServiceMembership.GetUserByUserName(userName);
// if no suer then user wasn't found or DB Errored
if (user == null)
{
throw new Exception("userName was not found");
}
// decrypt pw and see if they match with username's account
PasswordHash PH = new PasswordHash();
password = PH.GenerateHash(password);
if (user.HashWord != password)
{
throw new Exception("Password does not match the one on our records");
}
responseResult = true;
}
catch (Exception ex)
{
errMsg = ex.Message;
}
if (responseResult)
{
return Json(new
{
result = responseResult,
user = new
{
userId = user.UserID,
userName = user.UserName,
firstName = user.FirstName,
lastNmae = user.LastName,
email = user.Email
}
},
JsonRequestBehavior.AllowGet);
}
return Json(new
{
result = responseResult,
errorMessage = errMsg
},
JsonRequestBehavior.AllowGet);
}
The old Javascript code that called this method looks like this:
// gets user info from web service
loginUser: function (userName, password){
responseData = null;
$.ajax({
type: "GET",
url : app.CurrentCompanyDetails.WebServiceURL + this.apiRoot + this.loginUserCall,
dataType: "json",
data: {
userName: userName,
password: password
},
async: false,
crossDomain: true,
success: function(response) {
responseData = response;
},
error: function (xhr, status, msg) {
alert(msg);
}
});
return responseData;
},
I can't seem to find a definitive answer anywhere on how to accomplish this in C#
Here is an example showing how I make a Post call. I guess if you change to HttpMethod.GET it should work too.
My example sends userName and password to a REST API and if everything is ok, it returns the Cookie from the response. You can change it to adapt to your needs.
private static Cookie ConfigurarAcessoGED() {
Uri uri = new Uri("URL_FROM_API");
var req = new HttpRequestMessage(HttpMethod.Post, uri);
var reqBody = #"{
'UserName':'USERNAME',
'Password':'PASSWORD'
}";
req.Content = new StringContent(reqBody, Encoding.UTF8, "application/json");
CookieContainer cookies = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookies;
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
HttpResponseMessage resp = null;
try {
resp = client.SendAsync(req).Result;
} catch (Exception ex) {
throw new Exception("Something went wrong");
}
if (!resp.IsSuccessStatusCode) {
string message;
if (resp.StatusCode == HttpStatusCode.Unauthorized || resp.StatusCode == HttpStatusCode.Forbidden || resp.StatusCode == HttpStatusCode.Redirect)
message = "Permission denied.";
else
message = resp.ReasonPhrase;
throw new Exception(message);
}
IEnumerable<Cookie> responseCookies = cookies.GetCookies(uri).Cast<Cookie>();
if (responseCookies.FirstOrDefault() != null)
return responseCookies.FirstOrDefault();
return null;
}
Here is my method from my API:
[HttpPost, Route("LogIn")]
public IActionResult LogIn([FromServices] UserService userService, [FromBody]LoginModel login) {
using (var session = SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction()) {
User user = userService.Login(session, login.UserName, login.PassWord);
if (user == null)
return BadRequest("Invalid user");
var identity = new ClaimsIdentity("FileSystem");
identity.AddClaim(new Claim(ClaimTypes.Name, login.UserName));
foreach (Role r in user.Roles) {
identity.AddClaim(new Claim(ClaimTypes.Role, r.Nome));
}
var principal = new ClaimsPrincipal(identity);
HttpContext.Authentication.SignInAsync("FileSystem", principal).Wait();
}
return Ok();
}

System.Net.Http.HttpClient in [Universal Windows Platform] not working properly

I wrote simple method for getting data from (online) REST Service:
public async Task<Object> GetTask()
{
try
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://111.111.111.111:8080/");
HttpResponseMessage result = await client.GetAsync("ABC/CDE/getsomeinfo");
if (result.IsSuccessStatusCode)
{
//Deserialize
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Error" + ex);
}
return null;
}
Whenever i run this on UWP i'm getting catch exception:
The text associated with this error code could not be found.
A connection with the server could not be established
HResult 2147012867
Im trying to connect my client with restapi in internal network. In forms same code is working properly.
Try this
HttpResponseMessage response;
public async Task<string> webserviceResponse(string HttpMethod)
{
// check internet connection is available or not
if (NetworkInterface.GetIsNetworkAvailable() == true)
{
// CancellationTokenSource cts = new CancellationTokenSource(2000); // 2 seconds
HttpClient client = new HttpClient();
MultipartFormDataContent mfdc = new MultipartFormDataContent();
mfdc.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
string GenrateUrl = "your url";
if (HttpMethod == "POST")
{
response = await client.PostAsync(GenrateUrl, mfdc);
}
else if (HttpMethod == "PUT")
{
response = await client.PutAsync(GenrateUrl, mfdc);
}
else if (HttpMethod == "GET")
{
response = await client.GetAsync(GenrateUrl);
}
var respon = await response.Content.ReadAsStringAsync();
string convert_response = respon.ToString();
return convert_response;
}
else
{
return "0";
}
}

Categories

Resources