How to get the extension from ContentDisposition [duplicate] - c#

How do I get Content-Disposition parameters I returned from WebAPI controller using WebClient?
WebApi Controller
[Route("api/mycontroller/GetFile/{fileId}")]
public HttpResponseMessage GetFile(int fileId)
{
try
{
var file = GetSomeFile(fileId)
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(new MemoryStream(file));
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = file.FileOriginalName;
/********* Parameter *************/
response.Content.Headers.ContentDisposition.Parameters.Add(new NameValueHeaderValue("MyParameter", "MyValue"));
return response;
}
catch(Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
}
Client
void DownloadFile()
{
WebClient wc = new WebClient();
wc.DownloadDataCompleted += wc_DownloadDataCompleted;
wc.DownloadDataAsync(new Uri("api/mycontroller/GetFile/18"));
}
void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
WebClient wc=sender as WebClient;
// Try to extract the filename from the Content-Disposition header
if (!String.IsNullOrEmpty(wc.ResponseHeaders["Content-Disposition"]))
{
string fileName = wc.ResponseHeaders["Content-Disposition"].Substring(wc.ResponseHeaders["Content-Disposition"].IndexOf("filename=") + 10).Replace("\"", ""); //FileName ok
/****** How do I get "MyParameter"? **********/
}
var data = e.Result; //File OK
}
I'm returning a file from WebApi controller, I'm attaching the file name in the response content headers, but also I'd like to return an aditional value.
In the client I'm able to get the filename, but how do I get the aditional parameter?

If you are working with .NET 4.5 or later, consider using the System.Net.Mime.ContentDisposition class:
string cpString = wc.ResponseHeaders["Content-Disposition"];
ContentDisposition contentDisposition = new ContentDisposition(cpString);
string filename = contentDisposition.FileName;
StringDictionary parameters = contentDisposition.Parameters;
// You have got parameters now
Edit:
otherwise, you need to parse Content-Disposition header according to it's specification.
Here is a simple class that performs the parsing, close to the specification:
class ContentDisposition {
private static readonly Regex regex = new Regex(
"^([^;]+);(?:\\s*([^=]+)=((?<q>\"?)[^\"]*\\k<q>);?)*$",
RegexOptions.Compiled
);
private readonly string fileName;
private readonly StringDictionary parameters;
private readonly string type;
public ContentDisposition(string s) {
if (string.IsNullOrEmpty(s)) {
throw new ArgumentNullException("s");
}
Match match = regex.Match(s);
if (!match.Success) {
throw new FormatException("input is not a valid content-disposition string.");
}
var typeGroup = match.Groups[1];
var nameGroup = match.Groups[2];
var valueGroup = match.Groups[3];
int groupCount = match.Groups.Count;
int paramCount = nameGroup.Captures.Count;
this.type = typeGroup.Value;
this.parameters = new StringDictionary();
for (int i = 0; i < paramCount; i++ ) {
string name = nameGroup.Captures[i].Value;
string value = valueGroup.Captures[i].Value;
if (name.Equals("filename", StringComparison.InvariantCultureIgnoreCase)) {
this.fileName = value;
}
else {
this.parameters.Add(name, value);
}
}
}
public string FileName {
get {
return this.fileName;
}
}
public StringDictionary Parameters {
get {
return this.parameters;
}
}
public string Type {
get {
return this.type;
}
}
}
Then you can use it in this way:
static void Main() {
string text = "attachment; filename=\"fname.ext\"; param1=\"A\"; param2=\"A\";";
var cp = new ContentDisposition(text);
Console.WriteLine("FileName:" + cp.FileName);
foreach (DictionaryEntry param in cp.Parameters) {
Console.WriteLine("{0} = {1}", param.Key, param.Value);
}
}
// Output:
// FileName:"fname.ext"
// param1 = "A"
// param2 = "A"
The only thing that should be considered when using this class is it does not handle parameters (or filename) without a double quotation.
Edit 2:
It can now handle file names without quotations.

You can parse out the content disposition using the following framework code:
var content = "attachment; filename=myfile.csv";
var disposition = ContentDispositionHeaderValue.Parse(content);
Then just take the pieces off of the disposition instance.
disposition.FileName
disposition.DispositionType

With .NET Core 3.1 and more the most simple solution is:
using var response = await Client.SendAsync(request);
response.Content.Headers.ContentDisposition.FileName

The value is there I just needed to extract it:
The Content-Disposition header is returned like this:
Content-Disposition = attachment; filename="C:\team.jpg"; MyParameter=MyValue
So I just used some string manipulation to get the values:
void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
WebClient wc=sender as WebClient;
// Try to extract the filename from the Content-Disposition header
if (!String.IsNullOrEmpty(wc.ResponseHeaders["Content-Disposition"]))
{
string[] values = wc.ResponseHeaders["Content-Disposition"].Split(';');
string fileName = values.Single(v => v.Contains("filename"))
.Replace("filename=","")
.Replace("\"","");
/********** HERE IS THE PARAMETER ********/
string myParameter = values.Single(v => v.Contains("MyParameter"))
.Replace("MyParameter=", "")
.Replace("\"", "");
}
var data = e.Result; //File ok
}

As #Mehrzad Chehraz said you can use the new ContentDisposition class.
using System.Net.Mime;
// file1 is a HttpResponseMessage
FileName = new ContentDisposition(file1.Content.Headers.ContentDisposition.ToString()).FileName

Related

result of call back function return to view from model asynchronously

i have one call back function in my model. i.e void method.
how to pass that result to view.
my problem is while uploading file to amazon s3, it's returning progress value. i need to get that value in view.
my code as follows
public bool sendMyFileToS3(EmployeeModel e, string bucketName, string subDirectoryInBucket)
{
IAmazonS3 client = new AmazonS3Client(RegionEndpoint.USEast2);
TransferUtility utility = new TransferUtility(client);
TransferUtilityUploadRequest request = new TransferUtilityUploadRequest();
if (subDirectoryInBucket == "" || subDirectoryInBucket == null)
{
request.BucketName = bucketName; //no subdirectory just bucket name
}
else
{ // subdirectory and bucket name
request.BucketName = bucketName + #"/" + subDirectoryInBucket;
}
try
{
request.Key = RandomString() + Path.GetExtension(e.File.FileName); //file name up in S3
request.InputStream = e.File.InputStream;
request.CannedACL = S3CannedACL.PublicRead;
request.UploadProgressEvent += new EventHandler<UploadProgressArgs>(UploadFile_ProgressBar); //call bcak function
utility.Upload(request);
}
catch(AmazonS3Exception)
{
throw;
}
//commensing the transfer
//Generate link with expiry date.
Amazon.S3.Model.GetPreSignedUrlRequest aa = new Amazon.S3.Model.GetPreSignedUrlRequest();
aa.BucketName = request.BucketName;
aa.Key = request.Key;
aa.Expires = new DateTime().AddDays(2);
string url = client.GetPreSignedURL(aa);
url = url.Remove(url.IndexOf('?'));
return true;
}
public void UploadFile_ProgressBar(object sender, UploadProgressArgs e)
{
int pctProgress = (int)(e.TransferredBytes * 100 / e.TotalBytes);
}
every second "pctProgress" this parameter is replacing with new value. when ever replacing with new value it should pass to view.
how to do this.
please any suggestions.

acumatica import items with image API

Now I have successfully working code (with multiple threads) for items bulk import in IN202500 screen in Acumatica.
The problem is that I am struggling to import an image of an item and actually I don't have an image by itself but only URL link to this image.
So, my question is has anyone done this in c#?
This is my piece of code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ItemImportMultiThreaded
{
public class ItemImporter
{
private IN202500.Screen _itemsScreen;
private static object _itemsSchemaLock = new object();
private static IN202500.Content _itemsSchema;
public void Login(string url, string username, string password, string company)
{
Console.WriteLine("[{0}] Logging in to {1}...", System.Threading.Thread.CurrentThread.ManagedThreadId, url);
_itemsScreen = new IN202500.Screen();
_itemsScreen.Url = url + "/PMSDB/(W(2))/Soap/IN202500.asmx";
_itemsScreen.EnableDecompression = true;
_itemsScreen.CookieContainer = new System.Net.CookieContainer();
_itemsScreen.Timeout = 36000;
_itemsScreen.Login(username, password);
Console.WriteLine("[{0}] Logged in to {1}.", System.Threading.Thread.CurrentThread.ManagedThreadId, url);
lock (_itemsSchemaLock)
{
// Threads can share the same schema.
if (_itemsSchema == null)
{
Console.WriteLine("[{0}] Retrieving IN202500 schema...", System.Threading.Thread.CurrentThread.ManagedThreadId);
_itemsSchema = _itemsScreen.GetSchema();
if (_itemsSchema == null) throw new Exception("IN202500 GetSchema returned null. See AC-73433.");
}
}
}
public void Logout()
{
_itemsScreen.Logout();
}
public void Import(List<Item> items)
{
Console.WriteLine("[{0}] Submitting {1} items to Acumatica...", System.Threading.Thread.CurrentThread.ManagedThreadId, items.Count);
var commands = new IN202500.Command[]
{
_itemsSchema.StockItemSummary.InventoryID,
_itemsSchema.StockItemSummary.Description,
_itemsSchema.GeneralSettingsItemDefaults.ItemClass,
_itemsSchema.VendorDetails.VendorID,
_itemsSchema.VendorDetails.VendorInventoryID,
_itemsSchema.VendorDetails.ServiceCommands.NewRow,
_itemsSchema.VendorDetails.VendorID,
_itemsSchema.VendorDetails.VendorInventoryID,
_itemsSchema.VendorDetails.ServiceCommands.NewRow,
_itemsSchema.VendorDetails.VendorID,
_itemsSchema.VendorDetails.VendorInventoryID,
_itemsSchema.CrossReference.AlternateID,
_itemsSchema.CrossReference.Description,
_itemsSchema.Actions.Save
};
string[][] data = new string[items.Count][];
int count = 0;
foreach(Item item in items)
{
data[count] = new string[11];
data[count][0] = item.InventoryID;
data[count][1] = item.Description.Trim();
data[count][2] = item.ItemClassID;
data[count][3] = item.DigiKey;
data[count][4] = item.DKPN;
data[count][5] = item.Mouser;
data[count][6] = item.MouserID;
data[count][7] = item.Element14;
data[count][8] = item.Element14ID;
data[count][9] = item.AlternateID;
data[count][10] = item.Descr;
count++;
}
_itemsScreen.Import(commands, null, data, false, true, true);
Console.WriteLine("[{0}] Submitted {1} items to Acumatica.", System.Threading.Thread.CurrentThread.ManagedThreadId, items.Count);
}
}
}
I tried to use FileStream but that didn't work.
If by URL link you mean an external http resource, you can download the image and upload it.
The StockItems image field cycle through all the images contained in the Files popup in the order they are displayed:
I uploaded the images from a static external Url using the following code:
const string imageUrl = "https://cdn.acumatica.com/media/2016/03/software-technology-industries-small.jpg";
string path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetTempFileName(), ".jpg"));
// Download Image
using (WebClient client = new WebClient())
{
client.DownloadFile(new Uri(imageUrl), path);
}
// ReadUploadFile function below
byte[] data = ReadUploadFile(path);
_itemsScreen.Import(new IN202500.Command[]
{
// Get Inventory Item
new Value
{
Value = "D1",
LinkedCommand = _itemsSchema.StockItemSummary.InventoryID,
},
_itemsSchema.Actions.Save,
// Upload Inventory Item Image
new Value
{
FieldName = Path.GetFileName(path),
LinkedCommand = _itemsSchema.StockItemSummary.ServiceCommands.Attachment
},
_itemsSchema.Actions.Save
},
null,
new string[][]
{
new string[]
{
// Image data
Convert.ToBase64String(data)
}
},
false,
false,
true);
public byte[] ReadUploadFile(string filePath)
{
byte[] filedata;
using (FileStream file = File.Open(filePath,
FileMode.Open,
FileAccess.ReadWrite,
FileShare.ReadWrite))
{
filedata = new byte[file.Length];
file.Read(filedata, 0, filedata.Length);
}
if (filedata == null || filedata.Length == 0)
{
throw new Exception(string.Concat("Invalid or empty file: ", filePath));
}
return filedata;
}
You can try using the below, Tested Code.
var content = _context.CR306000GetSchema(); _context.CR306000Clear();
var commands = new List();
ReqParameter(content, ref commands);
commands.Add(content.Actions.Save);
commands.Add(content.CaseSummary.CaseID);
var orderResults = _context.CR306000Submit(commands.ToArray());
private static void ReqParameter(CR306000Content content, ref List cmds) { if (cmds == null) throw new ArgumentNullException("cmds");
private static void ReqParameter(CR306000Content content, ref List<Command> cmds)
{
if (cmds == null) throw new ArgumentNullException("cmds");
byte[] filedata= null;
Uri uri = new Uri("https://cdn.acumatica.com/media/2016/03/software-technology-industries-small.jpg"); // change the required url of the data that has to be fetched
if (uri.IsFile)
{
string filename = System.IO.Path.GetFileName(uri.LocalPath);
filedata = System.Text.Encoding.UTF8.GetBytes(uri.LocalPath);
}
if (filedata == null)
{
WebClient wc = new WebClient();
filedata = wc.DownloadData(uri);
}
cmds = new List<Command>
{
//Case Header Details
new Value { Value="<NEW>",LinkedCommand = content.CaseSummary.CaseID},
new Value { Value="L41",LinkedCommand = content.CaseSummary.ClassID},
new Value { Value="ABCSTUDIOS",LinkedCommand = content.CaseSummary.BusinessAccount, Commit = true},
new Value { Value="Test subject created from envelop call 11C",LinkedCommand = content.CaseSummary.Subject},
// body of the case
new Value{Value= "Body of the content for created through envelop call 11B", LinkedCommand = content.Details.Description},
//Attaching a file
new Value
{
Value = Convert.ToBase64String(filedata), // byte data that is passed to through envelop
FieldName = "Test.jpg",
LinkedCommand =
content.CaseSummary.ServiceCommands.Attachment
},
};
}
Let me know if this works for you.
Thanks

C# Web API method returns 403 Forbidden

Solved!!! - See last edit.
In my MVC app I make calls out to a Web API service with HMAC Authentication Filterign. My Get (GetMultipleItemsRequest) works, but my Post does not. If I turn off HMAC authentication filtering all of them work. I'm not sure why the POSTS do not work, but the GETs do.
I make the GET call from my code like this (this one works):
var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;
I make the POST call from my code like this (this one only works when I turn off HMAC):
private RestClient<Profile> profileClient = new RestClient<Profile>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
[HttpPost]
public ActionResult ProfileImport(IEnumerable<HttpPostedFileBase> files)
{
//...
var postResult = profileClient.PostRequest("api/Profile", newProfile).Result;
}
My RestClient builds like this:
public class RestClient<T> where T : class
{
//...
private void SetupClient(HttpClient client, string methodName, string apiUrl, T content = null)
{
const string secretTokenName = "SecretToken";
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_hmacSecret)
{
client.DefaultRequestHeaders.Date = DateTime.UtcNow;
var datePart = client.DefaultRequestHeaders.Date.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture);
var fullUri = _baseAddress + apiUrl;
var contentMD5 = "";
if (content != null)
{
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json); // <--- Javascript serialized version is hashed
}
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
var hmac = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
client.DefaultRequestHeaders.Add(secretTokenName, hmac);
}
else if (!string.IsNullOrWhiteSpace(_sharedSecretName))
{
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
client.DefaultRequestHeaders.Add(secretTokenName, sharedSecretValue);
}
}
public async Task<T[]> GetMultipleItemsRequest(string apiUrl)
{
T[] result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "GET", apiUrl);
var response = await client.GetAsync(apiUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T[]>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
public async Task<T> PostRequest(string apiUrl, T postObject)
{
T result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "POST", apiUrl, postObject);
var response = await client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter()).ConfigureAwait(false); //<--- not javascript formatted
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
//...
}
My Web API Controller is defined like this:
[SecretAuthenticationFilter(SharedSecretName = "xxxxxxxxxxxxxxx", HmacSecret = true)]
public class ProfileController : ApiController
{
[HttpPost]
[ResponseType(typeof(Profile))]
public IHttpActionResult PostProfile(Profile Profile)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
GuidValue = Guid.NewGuid();
Resource res = new Resource();
res.ResourceId = GuidValue;
var data23 = Resourceservices.Insert(res);
Profile.ProfileId = data23.ResourceId;
_profileservices.Insert(Profile);
return CreatedAtRoute("DefaultApi", new { id = Profile.ProfileId }, Profile);
}
}
Here is some of what SecretAuthenticationFilter does:
//now try to read the content as string
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var contentMD5 = content == "" ? "" : Hashing.GetHashMD5OfString(content); //<-- Hashing the non-JavaScriptSerialized
var datePart = "";
var requestDate = DateTime.Now.AddDays(-2);
if (actionContext.Request.Headers.Date != null)
{
requestDate = actionContext.Request.Headers.Date.Value.UtcDateTime;
datePart = requestDate.ToString(CultureInfo.InvariantCulture);
}
var methodName = actionContext.Request.Method.Method;
var fullUri = actionContext.Request.RequestUri.ToString();
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var expectedValue = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
// Are the hmacs the same, and have we received it within +/- 5 mins (sending and
// receiving servers may not have exactly the same time)
if (messageSecretValue == expectedValue
&& requestDate > DateTime.UtcNow.AddMinutes(-5)
&& requestDate < DateTime.UtcNow.AddMinutes(5))
goodRequest = true;
Any idea why HMAC doesn't work for the POST?
EDIT:
When SecretAuthenticationFilter tries to compare the HMAC sent, with what it thinks the HMAC should be they don't match. The reason is the MD5Hash of the content doesn't match the MD5Hash of the received content. The RestClient hashes the content using a JavaScriptSerializer.Serialized version of the content, but then the PostRequest passes the object as JsonMediaTypeFormatted.
These two types don't get formatted the same. For instance, the JavaScriptSerializer give's us dates like this:
\"EnteredDate\":\"\/Date(1434642998639)\/\"
The passed content has dates like this:
\"EnteredDate\":\"2015-06-18T11:56:38.6390407-04:00\"
I guess I need the hash to use the same data that's passed, so the Filter on the other end can confirm it correctly. Thoughts?
EDIT:
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the sent content (formatted via JSON) will match the hashed content.
I was not the person who wrote this code originally. :)
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the content used for the hash will be formatted as JSON and will match the sent content (which is also formatted via JSON).

Building a List with multiple URL Params in C#

I am trying to build a generic function that will run a simple HTTP Get request using various possible URL Params.
I want to be able to receive a flexible number of strings as a parameter and add them one by one as a URL parameter in the request.
Here's my code so far, I am trying to build a List but for some reason I just can't muster a workign solution..
public static void GetRequest(List<string> lParams)
{
lParams.Add(header1);
string myURL = "";
HttpWebRequest WebReq = (HttpWebRequest)WebRequest.Create(string.Format(myURL));
WebReq.Method = "GET";
HttpWebResponse WebResp = (HttpWebResponse)WebReq.GetResponse();
Stream Answer = WebResp.GetResponseStream();
StreamReader _Answer = new StreamReader(Answer);
sContent = _Answer.ReadToEnd();
}
Thanks!
I think you need this:
private static string CreateUrl(string baseUrl, Dictionary<string, string> args)
{
var sb = new StringBuilder(baseUrl);
var f = true;
foreach (var arg in args)
{
sb.Append(f ? '?' : '&');
sb.Append(WebUtility.UrlEncode(arg.Key) + '=' + WebUtility.UrlEncode(arg.Value));
f = false;
}
return sb.ToString();
}
Not so complex version with comments:
private static string CreateUrl(string baseUrl, Dictionary<string, string> parameters)
{
var stringBuilder = new StringBuilder(baseUrl);
var firstTime = true;
// Going through all the parameters
foreach (var arg in parameters)
{
if (firstTime)
{
stringBuilder.Append('?'); // first parameter is appended with a ? - www.example.com/index.html?abc=3
firstTime = false; // All other following parameters should be added with a &
}
else
{
stringBuilder.Append('&'); // all other parameters are appended with a & - www.example.com/index.html?abc=3&abcd=4&abcde=8
}
var key = WebUtility.UrlEncode(arg.Key); // Converting characters which are not allowed in the url to escaped values
var value = WebUtility.UrlEncode(arg.Value); // Same goes for the value
stringBuilder.Append(key + '=' + value); // Writing the parameter in the format key=value
}
return stringBuilder.ToString(); // Returning the url with parameters
}

Access additional twitter user info

I'm using Azure Mobile Services to authorize users and am now trying to get additional user info from the providers. I have it working for all of them except Twitter. To authenticate for all the other I'm using something similar to this:
var identities = await user.GetIdentitiesAsync();
var result = new JObject();
var fb = identities.OfType<FacebookCredentials>().FirstOrDefault();
if (fb != null)
{
var accessToken = fb.AccessToken;
result.Add("facebook", await GetProviderInfo("https://graph.facebook.com/me?access_token=" + accessToken));
}
Would I be able to do something like this:
var tw = identities.OfType<TwitterCredentials>().FirstOrDefault();
if (tw != null)
{
var accessToken = tw.AccessToken;
var accessTokenSecret = tw.AccessTokenSecret;
result.Add("twitter", await
GetProviderInfo("https://api.twitter.com/1.1/account/verify_credentials.json?token=" + accessToken + "&token_secret=" + accessTokenSecret + "&consumer_key=***************" + "&consumer_secret=******************************"));
}
or would I have to do something completely different?
Woops, just found a similar question here: Twitter single url request
Yes, it is possible, but it's more work than for other providers.
This is the code for your api controller (maybe needs some refactoring)
[HttpPost]
[Route("current/identity")]
public async Task<HttpResponseMessage> GetIdentityInfo()
{
var currentUser = User as ServiceUser;
if (currentUser != null)
{
var identities = await currentUser.GetIdentitiesAsync();
var googleCredentials = identities.OfType<GoogleCredentials>().FirstOrDefault();
if (googleCredentials != null)
{
var infos = await GetGoolgeDetails(googleCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var facebookCredentials = identities.OfType<FacebookCredentials>().FirstOrDefault();
if (facebookCredentials!= null)
{
var infos = await GetFacebookDetails(facebookCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var microsoftCredentials = identities.OfType<MicrosoftAccountCredentials>().FirstOrDefault();
if (microsoftCredentials != null)
{
var infos = await GetMicrosoftDetails(microsoftCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var twitterCredentials = identities.OfType<TwitterCredentials>().FirstOrDefault();
if (twitterCredentials != null)
{
var infos = await GetTwitterDetails(currentUser, twitterCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
private async Task<JToken> GetTwitterDetails(ServiceUser currentUser, TwitterCredentials twitterCredentials)
{
var twitterId = currentUser.Id.Split(':').Last();
var accessToken = twitterCredentials.AccessToken;
string consumerKey = ConfigurationManager.AppSettings["MS_TwitterConsumerKey"];
string consumerSecret = ConfigurationManager.AppSettings["MS_TwitterConsumerSecret"];
// Add this setting manually on your Azure Mobile Services Management interface.
// You will find the secret on your twitter app configuration
string accessTokenSecret = ConfigurationManager.AppSettings["FG_TwitterAccessTokenSecret"];
var parameters = new Dictionary<string, string>();
parameters.Add("user_id", twitterId);
parameters.Add("oauth_token", accessToken);
parameters.Add("oauth_consumer_key", consumerKey);
OAuth1 oauth = new OAuth1();
string headerString = oauth.GetAuthorizationHeaderString(
"GET", "https://api.twitter.com/1.1/users/show.json",
parameters, consumerSecret, accessTokenSecret);
var infos = await GetProviderInfo("https://api.twitter.com/1.1/users/show.json?user_id=" + twitterId, headerString);
return infos;
}
private async Task<JToken> GetMicrosoftDetails(MicrosoftAccountCredentials microsoftCredentials)
{
var accessToken = microsoftCredentials.AccessToken;
var infos = await GetProviderInfo("https://apis.live.net/v5.0/me/?method=GET&access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetFacebookDetails(FacebookCredentials facebookCredentials)
{
var accessToken = facebookCredentials.AccessToken;
var infos = await GetProviderInfo("https://graph.facebook.com/me?access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetGoolgeDetails(GoogleCredentials googleCredentials)
{
var accessToken = googleCredentials.AccessToken;
var infos = await GetProviderInfo("https://www.googleapis.com/oauth2/v3/userinfo?access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetProviderInfo(string url, string oauth1HeaderString = null)
{
using (var client = new HttpClient())
{
if (oauth1HeaderString != null)
{
client.DefaultRequestHeaders.Authorization = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(oauth1HeaderString);
}
var resp = await client.GetAsync(url).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
string rawInfo = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
return JToken.Parse(rawInfo);
}
}
Then you need this class to build a valid OAuth 1.0 authentication header:
(almost all of the following code ist from LinqToTwitter, https://linqtotwitter.codeplex.com)
public class OAuth1
{
const string OAUTH_VERSION = "1.0";
const string SIGNATURE_METHOD = "HMAC-SHA1";
const long UNIX_EPOC_TICKS = 621355968000000000L;
public string GetAuthorizationHeaderString(string method, string url, IDictionary<string, string> parameters, string consumerSecret, string accessTokenSecret)
{
string encodedAndSortedString = BuildEncodedSortedString(parameters);
string signatureBaseString = BuildSignatureBaseString(method, url, encodedAndSortedString);
string signingKey = BuildSigningKey(consumerSecret, accessTokenSecret);
string signature = CalculateSignature(signingKey, signatureBaseString);
string authorizationHeader = BuildAuthorizationHeaderString(encodedAndSortedString, signature);
return authorizationHeader;
}
internal void AddMissingOAuthParameters(IDictionary<string, string> parameters)
{
if (!parameters.ContainsKey("oauth_timestamp"))
parameters.Add("oauth_timestamp", GetTimestamp());
if (!parameters.ContainsKey("oauth_nonce"))
parameters.Add("oauth_nonce", GenerateNonce());
if (!parameters.ContainsKey("oauth_version"))
parameters.Add("oauth_version", OAUTH_VERSION);
if (!parameters.ContainsKey("oauth_signature_method"))
parameters.Add("oauth_signature_method", SIGNATURE_METHOD);
}
internal string BuildEncodedSortedString(IDictionary<string, string> parameters)
{
AddMissingOAuthParameters(parameters);
return
string.Join("&",
(from parm in parameters
orderby parm.Key
select parm.Key + "=" + PercentEncode(parameters[parm.Key]))
.ToArray());
}
internal virtual string BuildSignatureBaseString(string method, string url, string encodedStringParameters)
{
int paramsIndex = url.IndexOf('?');
string urlWithoutParams = paramsIndex >= 0 ? url.Substring(0, paramsIndex) : url;
return string.Join("&", new string[]
{
method.ToUpper(),
PercentEncode(urlWithoutParams),
PercentEncode(encodedStringParameters)
});
}
internal virtual string BuildSigningKey(string consumerSecret, string accessTokenSecret)
{
return string.Format(
CultureInfo.InvariantCulture, "{0}&{1}",
PercentEncode(consumerSecret),
PercentEncode(accessTokenSecret));
}
internal virtual string CalculateSignature(string signingKey, string signatureBaseString)
{
byte[] key = Encoding.UTF8.GetBytes(signingKey);
byte[] msg = Encoding.UTF8.GetBytes(signatureBaseString);
KeyedHashAlgorithm hasher = new HMACSHA1();
hasher.Key = key;
byte[] hash = hasher.ComputeHash(msg);
return Convert.ToBase64String(hash);
}
internal virtual string BuildAuthorizationHeaderString(string encodedAndSortedString, string signature)
{
string[] allParms = (encodedAndSortedString + "&oauth_signature=" + PercentEncode(signature)).Split('&');
string allParmsString =
string.Join(", ",
(from parm in allParms
let keyVal = parm.Split('=')
where parm.StartsWith("oauth") || parm.StartsWith("x_auth")
orderby keyVal[0]
select keyVal[0] + "=\"" + keyVal[1] + "\"")
.ToList());
return "OAuth " + allParmsString;
}
internal virtual string GetTimestamp()
{
long ticksSinceUnixEpoc = DateTime.UtcNow.Ticks - UNIX_EPOC_TICKS;
double secondsSinceUnixEpoc = new TimeSpan(ticksSinceUnixEpoc).TotalSeconds;
return Math.Floor(secondsSinceUnixEpoc).ToString(CultureInfo.InvariantCulture);
}
internal virtual string GenerateNonce()
{
return new Random().Next(111111, 9999999).ToString(CultureInfo.InvariantCulture);
}
internal virtual string PercentEncode(string value)
{
const string ReservedChars = #"`!##$^&*()+=,:;'?/|\[] ";
var result = new StringBuilder();
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
var escapedValue = Uri.EscapeDataString(value);
// Windows Phone doesn't escape all the ReservedChars properly, so we have to do it manually.
foreach (char symbol in escapedValue)
{
if (ReservedChars.IndexOf(symbol) != -1)
{
result.Append('%' + String.Format("{0:X2}", (int)symbol).ToUpper());
}
else
{
result.Append(symbol);
}
}
return result.ToString();
}
}

Categories

Resources