i'm trying to access a WebAPI which is using ValidateAntiForgeryToken. My WebAPI Method is this (a simple one), which is inside a User Controller (just for a test):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(String field)
{
String result = String.Empty;
if (ModelState.IsValid)
{
HtmlSanitizer sanitizer = new HtmlSanitizer();
try
{
result = sanitizer.Sanitize(field);
}
catch (Exception ex)
{
result = ex.Message;
throw;
}
}
return Json(result);
}
With Ajax, i can access it with ease:
$.ajax({
url: '/User/Test',
type: "POST",
contentType: "application/x-www-form-urlencoded",
data: {
field: self.textField(),
__RequestVerificationToken: $("input[name='__RequestVerificationToken']").val(),
},
success: function(e) {
self.textField(e)
self.divField(e);
},
error: function(e) {
console.log(e.error());
},
});
But, until now, i can't access this webapi with httpclient on xamarin. This is my code:
private async void DoTestWebApi()
{
try
{
HttpClient clientPage = new HttpClient()
{
BaseAddress = new Uri("https://localhost:44356/user")
};
var pageWithToken = await clientPage.GetAsync(clientPage.BaseAddress);
String verificationToken = GetVerificationToken(await pageWithToken.Content.ReadAsStringAsync());
HttpClient client = new HttpClient()
{
BaseAddress = new Uri("https://localhost:44356/user/test/")
};
HttpRequestMessage message = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44356/user/test/"),
Method = HttpMethod.Post
};
message.Headers.Add("__RequestVerificationToken", verificationToken);
String field = "teste";
//StringContent content = new StringContent("field=test", Encoding.UTF8, "application/x-www-form-urlencoded");
StringContent content = new StringContent("__RequestVerificationToken=" + verificationToken + ",field=test", Encoding.UTF8, "application/x-www-form-urlencoded");
// this doesn't work
//client.DefaultRequestHeaders.Add("__RequestVerificationToken", verificationToken);
var response2 = await client.SendAsync(message);
if (response2.IsSuccessStatusCode)
{
var t = response2.Content.ReadAsStringAsync();
if (true)
{
// just to check if t has value
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
Honestly, i don't know what else i could do to pass my anti forgery token inside the message. It works perfectly in ajax, i pass it inside the data content, but in xamarin it doesn't work.
All the code is executed inside the same localhost. If i remove the [ValidateAntiForgeryToken], it works.
What am i missing?
Edit:
Ok, so now i'm sending with cookies, but is not hitting the method again.
This is my update:
HttpClient clientPage = new HttpClient()
{
BaseAddress = new Uri("https://localhost:44356/user")
};
var pageWithToken = await clientPage.GetAsync(clientPage.BaseAddress);
String verificationToken = GetVerificationToken(await pageWithToken.Content.ReadAsStringAsync());
List<KeyValuePair<String, String>> cookiesInfo = new List<KeyValuePair<String, String>>();
foreach (var item in pageWithToken.Headers)
{
cookiesInfo.Add(new KeyValuePair<String, String>(item.Key, item.Value.ToString()));
}
cookiesInfo.Add(new KeyValuePair<string, string>("field", "value"));
cookiesInfo.Add(new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken));
CookieContainer cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
using (var client = new HttpClient(handler) { BaseAddress = new Uri("https://localhost:44356/user") })
{
var content = new FormUrlEncodedContent(cookiesInfo);
cookieContainer.Add(client.BaseAddress, new Cookie("__RequestVerificationToken", verificationToken));
foreach (var item in cookiesInfo)
{
cookieContainer.Add(client.BaseAddress, new Cookie(item.Key, item.Value));
}
var result = client.PostAsync(new Uri("https://localhost:44356/user/test"), content).Result;
result.EnsureSuccessStatusCode();
}
};
This is driving me nuts... Ok the test is in localhost but soon this app will be in Azure, and this is a pre-requisite...
Edit: GetVerificationToken Method:
private string GetVerificationToken(String verificationToken)
{
if (verificationToken != null && verificationToken.Length > 0)
{
verificationToken = verificationToken.Substring(verificationToken.IndexOf("__RequestVerificationToken"));
verificationToken = verificationToken.Substring(verificationToken.IndexOf("value=\"") + 7);
verificationToken = verificationToken.Substring(0, verificationToken.IndexOf("\""));
}
return verificationToken;
}
ValidateAntiForgeryToken is also expecting a cookie with __RequestVerificationToken and the value provided. This is to make sure that the one posting to the controller is the one who viewed the form.
Thanks to #Zroq tip, i've finally made it. The cookie was indeed missing. This is the final version of my method which sends data to a WebApi with AntiForgeryToken in Asp.NET MVC 5.0:
private async void DoTestWebApi()
{
try
{
CookieContainer cookieContainer = new CookieContainer();
HttpClientHandler handlerhttps = new HttpClientHandler
{
UseCookies = true,
UseDefaultCredentials = true,
CookieContainer = cookieContainer
};
HttpClient clientPage = new HttpClient(handlerhttps)
{
BaseAddress = new Uri("https://localhost:44356/user")
};
var pageWithToken = await clientPage.GetAsync(clientPage.BaseAddress);
String verificationToken = GetVerificationToken(await pageWithToken.Content.ReadAsStringAsync());
var cookies = cookieContainer.GetCookies(new Uri("https://localhost:44356/user"));
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer, UseDefaultCredentials = true, UseCookies = true })
{
using (var client = new HttpClient(handler) { BaseAddress = new Uri("https://localhost:44356/user/test") })
{
var contentToSend = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("field", "value"),
new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken),
});
var response = client.PostAsync(client.BaseAddress, contentToSend).Result;
}
};
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Thanks again #Zroq.
For anyone that want the GetVerificationToken() body :
private string GetVerification(string responseBody)
{
var data = QueryHelpers.ParseQuery(queryString: responseBody);
string firstValue = data[key: "<input name"];
var cutedValue = firstValue.Remove(startIndex: 0, count: 50);
var result = cutedValue.Split('"')[0];
return result;
}
Related
I have uploaded a file to SharePoint and found out what id it has. Now I need to update some of the other columns on that listitem. The problem is that System.Net.Http.HttpMethod.Patch doesn't exist.
public static async Task<string> UpdateFileData()
{
var (authResult, message) = await Authentication.AquireTokenAsync();
string updateurl = MainPage.rooturl + "lists/edd49389-7edb-41db-80bd-c8493234eafa/items/" + fileID + "/";
var httpClient = new HttpClient();
HttpResponseMessage response;
try
{
var root = new
{
fields = new Dictionary<string, string>
{
{ "IBX", App.IBX }, //column to update
{ "Year", App.Year}, //column to update
{ "Month", App.Month} //column to update
}
};
var s = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat };
var content = JsonConvert.SerializeObject(root, s);
var request = new HttpRequestMessage(HttpMethod.Put, updateurl);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
response = await httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
catch (Exception ex)
{
return ex.ToString();
}
}
Modify the code as below.
public static async Task<string> UpdateFileData()
{
var (authResult, message) = await Authentication.AquireTokenAsync();
string updateurl = MainPage.rooturl + "lists/edd49389-7edb-41db-80bd-c8493234eafa/items/" + fileID + "/";
var httpClient = new HttpClient();
HttpResponseMessage response;
try
{
var root = new
{
fields = new Dictionary<string, string>
{
{ "IBX", App.IBX }, //column to update
{ "Year", App.Year}, //column to update
{ "Month", App.Month} //column to update
}
};
var s = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat };
var content = JsonConvert.SerializeObject(root, s);
var request = new HttpRequestMessage(new HttpMethod("PATCH"), updateurl);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
request.Content = new StringContent(content, System.Text.Encoding.UTF8, "application/json;odata=verbose");
response = await httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
catch (Exception ex)
{
return ex.ToString();
}
}
Or we can also use REST API to update list item by ID.
Refer to: SharePoint 2013 REST Services using C# and the HttpClient
It dependents whether .NET Core or .NET Framework is utilized, in case of `.NET Core HttpClient.PatchAsync Method could be utilized.
In case of .NET Framework ListItem could be updated like this:
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri("https://graph.microsoft.com");
var listItemPayload = new Dictionary<string, object>
{
{"Color", "Fuchsia"},
{"Quantity", 934}
};
var requestContent = new StringContent(JsonConvert.SerializeObject(listItemPayload));
requestContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var response = await client.PatchAsync(new Uri($"https://graph.microsoft.com/v1.0/sites/{siteId}/lists/{listId}/items/{itemId}/fields"), requestContent);
var data = response.Content.ReadAsStringAsync().Result.ToString();
}
where PatchAsync is the extension method for HttpClient class:
public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync(this HttpClient client, Uri requestUri, HttpContent iContent)
{
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, requestUri)
{
Content = iContent
};
HttpResponseMessage response = new HttpResponseMessage();
try
{
response = await client.SendAsync(request);
}
catch (TaskCanceledException e)
{
Debug.WriteLine("ERROR: " + e.ToString());
}
return response;
}
}
All the credits for extension method go to the author of this answer
Can't you just use the HttpMethod class constructor?
new HttpMethod("PATCH");
Source: https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpmethod.-ctor?view=netframework-4.7.2#System_Net_Http_HttpMethod__ctor_System_String_
I'm trying to POST data to a WebService with SSL Certification.
It's working fine in Emulator but the Device is keeping stuck on GetResponseAsync(). I tried it with HttpClient with and without ModernHttpClient-Library but it stuck on PostAsync(), so it doesn't work too.
One Solution i tried of another Thread is just to add
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
^but it's also not working
In Manifest i added Permissions for Internet.
Thats the Code with GetResponseAsync()
public async Task<Response<V>> CallWebServiceAsync<T, V>(string ticket, string url, T requestData)
{
try
{
var request = new Request<T>
{
Ticket = ticket,
Data = requestData
};
DataContractJsonSerializer serializer = new DataContractJsonSerializer(request.GetType(), this._knownTypes);
var httpRequest = WebRequest.CreateHttp(url);
httpRequest.Method = "POST";
httpRequest.UseDefaultCredentials = true;
httpRequest.ContentType = "application/json";
var requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false);
serializer.WriteObject(requestStream, request);
var response = await httpRequest.GetResponseAsync().ConfigureAwait(false);
var resultStream = response.GetResponseStream();
if (resultStream == null)
{
throw new FailedWebServiceInteractionException($"Got no response stream from request. URL:{url}");
}
var jsonResponseSerializer = new DataContractJsonSerializer(typeof(Response<V>), this._knownTypes);
Response<V> result = (Response<V>)jsonResponseSerializer.ReadObject(resultStream);
resultStream.Dispose();
return result;
}
catch (Exception e)
{
throw new FailedWebServiceInteractionException("Deserialization from json response failed", e);
}
}
Code with PostAsync() (+- ModernHttpClient)
public async Task<Response<V>> CallWebServiceAsync<T, V>(string ticket, string url, T requestData)
{
try
{
var request = new Request<T>
{
Ticket = ticket,
Data = requestData
};
var httpClientHandler = new NativeMessageHandler
{
UseDefaultCredentials = true
};
var httpRequest = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(url)
};
httpRequest.DefaultRequestHeaders.Accept.Clear();
httpRequest.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = JsonConvert.SerializeObject(request);
var response =
await
httpRequest.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"))
.ConfigureAwait(false);
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<Response<V>>(jsonString);
}
catch (Exception e)
{
throw new FailedWebServiceInteractionException("Deserialization from json response failed", e);
}
}
I don't know if the certificates are the problem or something else.
They're not self-signed.
I found a solution. Time is gone since then. I don't know the exact solution but here is my code that works fine now:
var request = new Request<T>
{
Ticket = ticket,
Data = requestData
};
var httpClientHandler = new OkHttpClientHandler
{
UseDefaultCredentials = true,
AllowAutoRedirect = true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ClientCertificateOptions = ClientCertificateOption.Automatic
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(url),
Timeout = TimeSpan.FromSeconds(15)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonRequest = this.SerializeToJson(request);
var compressedRequest = StringCompressor.CompressString(jsonRequest);
var httpRequest = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new ByteArrayContent(compressedRequest)
{
Headers = {ContentType = new MediaTypeHeaderValue("application/json")}
}
};
var httpResponse = await client.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
client.Dispose();
httpResponse.EnsureSuccessStatusCode();
var compressedResponse = await httpResponse.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
httpResponse.Dispose();
var jsonResponse = StringCompressor.DecompressString(compressedResponse);
var jsonResponseSerializer = new DataContractJsonSerializer(typeof(Response<V>), this._knownTypes);
Response<V> result;
using (var jsonResponseStream = this.GenerateStreamFromString(jsonResponse))
{
result = (Response<V>) jsonResponseSerializer.ReadObject(jsonResponseStream);
}
return result;
I'm using a self compiled wrapper of OkHttpClient library. I think ModernHttpClient (it is referencing OkHttp too) works too. Someone needs to test it.
Important to know is any other project that references this project need the library too.
App.Base Project - Implemented the above code to connect to
Webservice
App Project - References to App.Base. Need the library
too.
I was using the following code to send fromData contains 2 values (File and String) to a WebAPI using javascript.
var formData = new FormData();
formData.append('name', 'previewImg');
formData.append('upload', $('input[type=file]')[0].files[0]);
$.ajax({
url: 'WebAPI url',
data: formData,
contentType: false,
processData: false,
// ... Other options like success and etc
})
I want to do the same thing using C# Windows Application, I need to write a Method accepts 2 paramters (FilePath and String) then send the file and the string to WebAPI.
I tried the following code but it returns an error from the service( I am trying to contact with koemei upload service) , although it works fine when I call it from Js :
void SendData(string filepath,string name){
var url = "URL";
HttpContent fileContent = new ByteArrayContent(System.IO.File.ReadAllBytes(filepath));
using (var client = new HttpClient())
{
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileContent, "upload");
formData.Add(new StringContent(name), "name");
//call service
var response = client.PostAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
{
throw new Exception();
}
else
{
if (response.Content.GetType() != typeof(System.Net.Http.StreamContent))
throw new Exception();
var stream = response.Content.ReadAsStreamAsync();
var content = stream.Result;
var path = #"name.txt";
using (var fileStream = System.IO.File.Create(path))
{
content.CopyTo(fileStream);
}
}
}
}
}
Here is a sample
private List<ByteArrayContent> GetFileByteArrayContent(HashSet<string> files)
{
List<ByteArrayContent> list = new List<ByteArrayContent>();
foreach (var file in files)
{
var fileContent = new ByteArrayContent(File.ReadAllBytes(file));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = Path.GetFileName(file)
};
list.Add(fileContent);
}
return list;
}
private List<ByteArrayContent> GetFormDataByteArrayContent(NameValueCollection collection)
{
List<ByteArrayContent> list = new List<ByteArrayContent>();
foreach (var key in collection.AllKeys)
{
var dataContent = new ByteArrayContent(Encoding.UTF8.GetBytes(collection[key]));
dataContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
Name = key
};
list.Add(dataContent);
}
return list;
}
And here is how to post the data and files
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));//set how to get data
using (var content = new MultipartFormDataContent())//post by content type multipart/form-data
{
NameValueCollection dataCollection;//the datas you want to post
HashSet<string> filePaths;//the files you want to post
var formDatas = this.GetFormDataByteArrayContent(dataCollection);//get collection
var files = this.GetFileByteArrayContent(filePaths);//get collection
Action<List<ByteArrayContent>> act = (dataContents) =>
{//declare an action
foreach (var byteArrayContent in dataContents)
{
content.Add(byteArrayContent);
}
};
act(formDatas);//process act
act(files);//process act
try
{
var result = client.PostAsync(this.txtUrl.Text, content).Result;//post your request
}
catch (Exception ex)
{
//error
}
}
}
I have problem.
I have code on the main page:
private async Task<string> Login(string username, string password)
{
try
{
string url = "myurl.com/page1";
Uri address = new Uri(url);
var postData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Login", username),
new KeyValuePair<string, string>("Password", password)
};
CookieContainer cookieJar = new CookieContainer();
HttpContent content = new FormUrlEncodedContent(postData);
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false
};
var client = new HttpClient(handler)
{
BaseAddress = address
};
HttpResponseMessage response = await client.PostAsync(url, content);
string UrlBase = "myurl.com/page1";
Uri uri = new Uri(UrlBase);
var responseCookies = cookieJar.GetCookies(uri);
foreach (Cookie cookie in responseCookies)
{
string cookieName = cookie.Name;
string cookieValue = cookie.Value;
}
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
return body;
}
catch (Exception e)
{
return e.ToString();
}
}
But in debug I cant see cookies. And his number is 0.
And I don`t know, how can I get myurl.com/page2 on the Page2.xaml?
How to use and save cookies?
How to get and set cookies for Windows Phone 8
http://msdn.microsoft.com/en-us/library/windowsphone/develop/dd920298(v=vs.105).aspx
I've got the following code that works successfully. I can't figure out how to get the cookie out of the response. My goal is that I want to be able to set cookies in the request and get cookies out of the response. Thoughts?
private async Task<string> Login(string username, string password)
{
try
{
string url = "http://app.agelessemail.com/account/login/";
Uri address = new Uri(url);
var postData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password ", password)
};
HttpContent content = new FormUrlEncodedContent(postData);
var cookieJar = new CookieContainer();
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false
};
var client = new HttpClient(handler)
{
BaseAddress = address
};
HttpResponseMessage response = await client.PostAsync(url,content);
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
return body;
}
catch (Exception e)
{
return e.ToString();
}
}
Here is the complete answer:
HttpResponseMessage response = await client.PostAsync(url,content);
response.EnsureSuccessStatusCode();
Uri uri = new Uri(UrlBase);
var responseCookies = cookieJar.GetCookies(uri);
foreach (Cookie cookie in responseCookies)
{
string cookieName = cookie.Name;
string cookieValue = cookie.Value;
}
To add cookies to a request, populate the cookie container before the request with CookieContainer.Add(uri, cookie). After the request is made the cookie container will automatically be populated with all the cookies from the response. You can then call GetCookies() to retreive them.
CookieContainer cookies = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookies;
HttpClient client = new HttpClient(handler);
HttpResponseMessage response = client.GetAsync("http://google.com").Result;
Uri uri = new Uri("http://google.com");
IEnumerable<Cookie> responseCookies = cookies.GetCookies(uri).Cast<Cookie>();
foreach (Cookie cookie in responseCookies)
Console.WriteLine(cookie.Name + ": " + cookie.Value);
Console.ReadLine();
There's alternative if you don't have access to the HttpClient and can't inject the CookieContainer. This works in .NET Core 2.2:
private string GetCookie(HttpResponseMessage message)
{
message.Headers.TryGetValues("Set-Cookie", out var setCookie);
var setCookieString = setCookie.Single();
var cookieTokens = setCookieString.Split(';');
var firstCookie = cookieTokens.FirstOrDefault();
var keyValueTokens = firstCookie.Split('=');
var valueString = keyValueTokens[1];
var cookieValue = HttpUtility.UrlDecode(valueString);
return cookieValue;
}
You can easily get a cookie value with the given URL.
private async Task<string> GetCookieValue(string url, string cookieName)
{
var cookieContainer = new CookieContainer();
var uri = new Uri(url);
using (var httpClientHandler = new HttpClientHandler
{
CookieContainer = cookieContainer
})
{
using (var httpClient = new HttpClient(httpClientHandler))
{
await httpClient.GetAsync(uri);
var cookie = cookieContainer.GetCookies(uri).Cast<Cookie>().FirstOrDefault(x => x.Name == cookieName);
return cookie?.Value;
}
}
}
Not in every case you can add httpClientHandler to httpClient. For example, when you use integration tests testServer.CreateClient() or inject httpClient from IHttpClientFactory. So, I have simply read values from header.
public static List<Cookie> GetCookies(this HttpResponseMessage message)
{
message.Headers.TryGetValues("Set-Cookie", out var cookiesHeader);
var cookies = cookiesHeader.Select(cookieString => CreateCookie(cookieString)).ToList();
return cookies;
}
private static Cookie CreateCookie(string cookieString)
{
var properties = cookieString.Split(';', StringSplitOptions.TrimEntries);
var name = properties[0].Split("=")[0];
var value = properties[0].Split("=")[1];
var path = properties[2].Replace("path=", "");
var cookie = new Cookie(name, value, path)
{
Secure = properties.Contains("secure"),
HttpOnly = properties.Contains("httponly"),
Expires = DateTime.Parse(properties[1].Replace("expires=", ""))
};
return cookie;
}
CreateCookie method may be modified to exactly match your cookie properties.