Interactive Brokers - I keep getting invalid consumer while requesting OAuth token - c#

I have a valid consumer key and I'm unable to get Interactive Brokers Web API OAuth to work.
{"error":"id: 3931, error: invalid consumer","statusCode":401}
The endpoint I'm trying to get to work: https://www.interactivebrokers.com/webtradingapi/doc.html#tag/OAuth/paths/~1oauth~1request_token/post
OAuth v1.0a specification https://oauth.net/core/1.0a/#auth_header_authorization
Am I missing something in my implementation?
It's also on GitHub.
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://www.interactivebrokers.com/tradingapi/v1/")
};
var restClient = new IBRestClient(httpClient);
var response = await restClient.RequestTokenAsync("xxxxx");
Console.WriteLine($"Response: {response}");
Console.ReadLine();
public sealed class IBRestClient
{
private readonly HttpClient _httpClient;
public IBRestClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async ValueTask<string> RequestTokenAsync(string consumerKey)
{
const string requestUri = "oauth/request_token";
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
var baseUrl = _httpClient.BaseAddress!.AbsoluteUri;
var authorizationHeader = OAuthHelper.GetAuthorizationHeader($"{baseUrl}{requestUri}", "POST", consumerKey);
var authSplit = authorizationHeader.Split(' ');
request.Headers.Authorization = new AuthenticationHeaderValue(authSplit[0], authSplit[1]);
var response = await _httpClient.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
}
public static class OAuthHelper
{
private static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create();
public static string GetAuthorizationHeader(string uri, string method, string consumerKey)
{
var oauthParameters = new Dictionary<string, string>
{
{ "oauth_consumer_key", consumerKey },
{ "oauth_signature_method", "RSA-SHA256" },
{ "oauth_timestamp", GetTimestamp() },
{ "oauth_nonce", GetNonce() },
{ "oauth_callback", "oob" }
};
// The request parameters are collected, sorted and concatenated into a normalized string
var queryParameters = ExtractQueryParams(uri);
var oauthParamString = GetOAuthParamString(queryParameters, oauthParameters);
var baseUri = GetBaseUriString(uri);
// Signature Base String
var signatureBaseString = GetSignatureBaseString(baseUri, method, oauthParamString);
var pem = File.ReadAllText("private_encryption.pem");
var signingKey = RSA.Create();
signingKey.ImportFromPem(pem);
var signature = SignSignatureBaseString(signatureBaseString, Encoding.UTF8, signingKey);
oauthParameters.Add("oauth_signature", signature);
// Constructs and returns the Authorization header
var sb = new StringBuilder();
foreach (var param in oauthParameters)
{
sb
.Append(sb.Length == 0 ? "OAuth " : ",")
.Append(param.Key)
.Append("=\"")
.Append(ToUriRfc3986(param.Value))
.Append('"');
}
return sb.ToString();
}
/// <summary>
/// Parse query parameters out of the URL.
/// </summary>
private static Dictionary<string, List<string>> ExtractQueryParams(string uri)
{
var queryParamCollection = new Dictionary<string, List<string>>();
var beginIndex = uri.IndexOf('?');
if (beginIndex <= 0)
{
return queryParamCollection;
}
var rawQueryString = uri[beginIndex..];
var decodedQueryString = Uri.UnescapeDataString(rawQueryString);
var mustEncode = !decodedQueryString.Equals(rawQueryString);
var queryParams = rawQueryString.Split('&', '?');
foreach (var queryParam in queryParams)
{
if (string.IsNullOrEmpty(queryParam))
{
continue;
}
var separatorIndex = queryParam.IndexOf('=');
var key = separatorIndex < 0 ? queryParam : Uri.UnescapeDataString(queryParam[..separatorIndex]);
var value = separatorIndex < 0
? string.Empty
: Uri.UnescapeDataString(queryParam[(separatorIndex + 1)..]);
var encodedKey = mustEncode ? ToUriRfc3986(key) : key;
var encodedValue = mustEncode ? ToUriRfc3986(value) : value;
if (!queryParamCollection.ContainsKey(encodedKey))
{
queryParamCollection[encodedKey] = new List<string>();
}
queryParamCollection[encodedKey].Add(encodedValue);
}
return queryParamCollection;
}
/// <summary>
/// Lexicographically sorts all parameters and concatenates them into a string.
/// </summary>
private static string GetOAuthParamString(IDictionary<string, List<string>> queryParameters,
IDictionary<string, string> oauthParameters)
{
var sortedParameters = new SortedDictionary<string, List<string>>(queryParameters, StringComparer.Ordinal);
foreach (var oauthParameter in oauthParameters)
{
sortedParameters[oauthParameter.Key] = new List<string> { oauthParameter.Value };
}
// Build the OAuth parameter string
var parameterString = new StringBuilder();
foreach (var parameter in sortedParameters)
{
var values = parameter.Value;
values.Sort(StringComparer.Ordinal); // Keys with same name are sorted by their values
foreach (var value in values)
{
parameterString
.Append(parameterString.Length > 0 ? "&" : string.Empty)
.Append(parameter.Key)
.Append('=')
.Append(value);
}
}
return parameterString.ToString();
}
/// <summary>
/// Normalizes the URL.
/// </summary>
private static string GetBaseUriString(string uriString)
{
var uri = new Uri(uriString);
var lowerCaseScheme = uri.Scheme.ToLower();
var lowerCaseAuthority = uri.Authority.ToLower();
var path = uri.AbsolutePath;
if (("http".Equals(lowerCaseScheme) && uri.Port == 80) || ("https".Equals(lowerCaseScheme) && uri.Port == 443))
{
// Remove port if it matches the default for scheme
var index = lowerCaseAuthority.LastIndexOf(':');
if (index >= 0)
{
lowerCaseAuthority = lowerCaseAuthority[..index];
}
}
if (string.IsNullOrEmpty(path))
{
path = "/";
}
return $"{lowerCaseScheme}://{lowerCaseAuthority}{path}"; // Remove query and fragment
}
/// <summary>
/// The Signature Base String is a consistent reproducible concatenation of the request elements into a single string.
/// </summary>
private static string GetSignatureBaseString(string baseUri, string httpMethod, string oauthParamString)
{
return httpMethod.ToUpper() // Uppercase HTTP method
+ "&" + ToUriRfc3986(baseUri) // Base URI
+ "&" + ToUriRfc3986(oauthParamString); // OAuth parameter string
}
/// <summary>
/// Signs the signature base string using an RSA private key.
/// </summary>
private static string SignSignatureBaseString(string baseString, Encoding encoding, RSA privateKey)
{
var hash = Sha256Digest(baseString, encoding);
var signedHashValue = privateKey.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signedHashValue);
}
/// <summary>
/// Percent encodes entities.
/// </summary>
private static string ToUriRfc3986(string input)
{
if (string.IsNullOrEmpty(input))
{
return input;
}
var escaped = new StringBuilder(Uri.EscapeDataString(input));
string[] uriRfc3986EscapedChars = { "!", "*", "'", "(", ")" };
foreach (var escapedChar in uriRfc3986EscapedChars)
{
escaped.Replace(escapedChar, UriHelper.HexEscape(escapedChar[0]));
}
return escaped.ToString();
}
/// <summary>
/// Returns a cryptographic hash of the given input.
/// </summary>
private static byte[] Sha256Digest(string input, Encoding encoding)
{
var inputBytes = encoding.GetBytes(input);
return SHA256.HashData(inputBytes);
}
/// <summary>
/// Generates a 16 char random string for replay protection.
/// </summary>
private static string GetNonce()
{
var data = new byte[8];
Random.GetBytes(data);
return BitConverter.ToString(data).Replace("-", string.Empty).ToLower();
}
/// <summary>
/// Returns UNIX Timestamp.
/// </summary>
private static string GetTimestamp()
{
return DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
}
}
internal static class UriHelper
{
private static readonly char[] HexUpperChars =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
internal static string HexEscape(char character)
{
if (character > '\xff')
{
throw new ArgumentOutOfRangeException(nameof(character));
}
var chars = new char[3];
var pos = 0;
EscapeAsciiChar(character, chars, ref pos);
return new string(chars);
}
private static void EscapeAsciiChar(char ch, char[] to, ref int pos)
{
to[pos++] = '%';
to[pos++] = HexUpperChars[(ch & 0xf0) >> 4];
to[pos++] = HexUpperChars[ch & 0xf];
}
}

Related

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();
}
}

Get String with Regex

I am using to get String from Another String with this :
string t = "function " + funcName + "(.*?)};";
Regex regex = new Regex(t, RegexOptions.Singleline);
var v2 = regex2.Match(html);
string func = v2.Groups[1].Value;
I need to get this String :
a=a.split("");a=a.reverse();a=a.slice(1);a=a.reverse();return a.join("")
from :
function $n(a){a=a.split("");a=a.reverse();a=a.slice(1);a=a.reverse();return a.join("")};
That can be too:
someCodefunction Ab(a){a=a.split("");a=a.reverse();a=a.slice(1);a=a.reverse();return a.join("")};someCode
The funcName is : $n OR 'Ab'
I tried this code but sometimes it won't return the function content.Any idea what can be the problem?
YouTube has changed signature decode function. I am using below functions to decode youtube encoded signature. Regex seems so complicated to me, so i use "GetBetweenInString" function instead.
/// <summary>
///
/// </summary>
/// <param name="YouTubeVideoSignatureEncoded">encoded youtube signature</param>
/// <param name="html5playerJScode">html5player-(.+?)\.js</param>
/// <returns></returns>
public string DecodeYouTubeSignature(string YouTubeVideoSignatureEncoded, string html5playerJScode)
{
//JScode=html5player javascript code
string Function_Name = GetBetweenInString(html5playerJScode, ".signature=$", "(");
//find the decoder function line
string functionLine = GetBetweenInString(html5playerJScode, "function $" + Function_Name, "};");
string[] Lines = functionLine.Split(';');
for (int i = 0; i <= Lines.Length - 1; i++)
{
string Line = Lines[i].Trim();
string jsVariable = GetBetweenInString(Line, "(", ")").Trim();
if (Line.ToLower().Contains(".reverse"))
{
char[] charArray = YouTubeVideoSignatureEncoded.ToCharArray();
Array.Reverse(charArray);
YouTubeVideoSignatureEncoded = new string(charArray);
}
else if (Line.ToLower().Contains(".slice"))
{
YouTubeVideoSignatureEncoded = YouTubeVideoSignatureEncoded.Substring(Convert.ToInt32(jsVariable));
}
}
return YouTubeVideoSignatureEncoded;// return decoded signature if possible.
}
/// <summary>
/// string stackoverflow = GetBetweenInString("http://stackoverflow.com", "http://", ".com");
/// </summary>
/// <param name="str">http://stackoverflow.com</param>
/// <param name="fromStr">http://</param>
/// <param name="toStr">.com</param>
/// <returns>stackoverflow</returns>
public string GetBetweenInString(string str, string fromStr, string toStr)
{
try
{
if (string.IsNullOrEmpty(str) == true)
return "";
if (string.IsNullOrEmpty(fromStr) == true)
return "";
if (string.IsNullOrEmpty(toStr) == true)
return "";
string[] R = System.Text.RegularExpressions.Regex.Split(str, fromStr);
if (R.Length == 1)
return "";
string[] R1 = System.Text.RegularExpressions.Regex.Split(R[1], toStr);
if (R1.Length == 1)
{
return "";
}
else
{
return R1[0];
}
}
catch
{
return "";
}
}

How to return JSON to browser from model class using SignalR and URL call to Web API?

Here's what's going on. I have an ASP.NET MVC 4 Web API web application. I can call API resources via URL. One of these functions get performance monitoring data for a specified amount of time and returns it in JSON once it has completed. However, what I want to do is return
It is IMPORTANT to note that I am working with a the browser and API resources in the model, not with a View. Please don't casually tell me to use Javascript in a View, because there is no view, or tell me to look at the SignalR wiki because the information for ".NET" sections is meant for desktop applications, not web apps. For example, you can't "Console.WriteLine()" to a browser.
To reiterate, I am using ASP.NET MVC 4 Web API to develop an API, and am calling the API via URL in the browser and it is returning JSON. I am attempting to use SignalR to have the app send JSON to the browser, but it is not doing anything at all. Rather, the application simply returns the completed JSON from the controller action with all of the performance data values once the process has completed. In other words, SignalR is not working.
So what I'm trying to do is while the API resource is gathering all the information, SignalR sends JSON to the browser every second so that the client can see what's going on in real time.
What I need to find out is why SignalR isn't sending it, and how I can send information to be displayed in the browser without Javascript, since I'm working from a model class, not from a view.
As you can see, I subscribe to the event using On, and then use Invoke to call the server-side hub method SendToClient.
Please let me know if I'm trying to do is impossible. I have never heard of a "real-time", dynamic API call via URL.
Here is my hub class. It is located in ~/signalr/hubs and is in a file called LiveHub.cs. The method Send is what I am trying to invoke in the method seen in the next code block.
namespace PerfMon2.signalr.hubs
{
public class LiveHub : Hub
{
public void SendToClient(List<DataValueInfo> json)
{
Clients.showValue(json);
}
}
}
Here is the method from LogDBRepository.cs that includes the SignalR calls.
public List<LogInfo> LogTimedPerfData(string macName, string categoryName, string counterName,
string instanceName, string logName, string live, long? seconds)
{
iModsDBRepository modsDB = new iModsDBRepository();
List<MachineInfo> theMac = modsDB.GetMachineByName(macName);
if (theMac.Count == 0)
return new List<LogInfo>();
else if (instanceName == null)
{
if (!PerformanceCounterCategory.Exists(categoryName, macName) ||
!PerformanceCounterCategory.CounterExists(counterName, categoryName, macName) )
{
return new List<LogInfo>();
}
}
else if (instanceName != null)
{
if (!PerformanceCounterCategory.Exists(categoryName, macName) ||
!PerformanceCounterCategory.CounterExists(counterName, categoryName, macName) ||
!PerformanceCounterCategory.InstanceExists(instanceName, categoryName, macName))
{
return new List<LogInfo>();
}
}
else if (logName == null)
{
return new List<LogInfo>();
}
// Check if entered log name is a duplicate for the authenticated user
List<LogInfo> checkDuplicateLog = this.GetSingleLog(logName);
if (checkDuplicateLog.Count > 0)
{
return new List<LogInfo>();
}
PerformanceCounterCategory category = new PerformanceCounterCategory(categoryName, theMac[0].MachineName);
if (category.CategoryName == null || category.MachineName == null)
{
return new List<LogInfo>();
}
List<LogInfo> logIt = new List<LogInfo>();
if (category.CategoryType != PerformanceCounterCategoryType.SingleInstance)
{
List<InstanceInfo> instances = modsDB.GetInstancesFromCatMacName(theMac[0].MachineName, category.CategoryName);
foreach (InstanceInfo inst in instances)
{
if (!category.InstanceExists(inst.InstanceName))
{
continue;
}
else if (inst.InstanceName.Equals(instanceName, StringComparison.OrdinalIgnoreCase))
{
PerformanceCounter perfCounter = new PerformanceCounter(categoryName, counterName,
inst.InstanceName, theMac[0].MachineName);
//CounterSample data = perfCounter.NextSample();
//double value = CounterSample.Calculate(data, perfCounter.NextSample());
string data = "";
List<UserInfo> currUser = this.GetUserByName(WindowsIdentity.GetCurrent().Name);
string timeStarted = DateTime.Now.ToString("MM/dd/yyyy - h:mm:ss tt");
//string[] dataValues = new string[(int)seconds];
List<string> dataValues = new List<string>();
var hubConnection = new HubConnection("http://localhost/PerfMon2/");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
var perfMon = hubConnection.CreateProxy("LiveHub");
// perfMon.On("sendValue", message => Console.WriteLine(message));
perfMon.On("showValue", json => Console.WriteLine(json));
hubConnection.Start().Wait();
List<DataValueInfo> lol = new List<DataValueInfo>();
for (int i = 0; i < seconds; i++)
{
data = "Value " + i + ": " + perfCounter.NextValue().ToString();
//dataValues[i] = data;
dataValues.Add(data);
lol.Add(new DataValueInfo
{
Value = perfCounter.NextValue().ToString()
});
// perfMon.Invoke<List<DataValueInfo>>("Send", lol);
perfMon.Invoke("SendToClient", lol);
Thread.Sleep(1000);
}
string timeFinished = DateTime.Now.ToString("MM/dd/yyyy - h:mm:ss tt");
Log log = new Log
{
LogName = logName,
CounterName = perfCounter.CounterName,
InstanceName = perfCounter.InstanceName,
CategoryName = perfCounter.CategoryName,
MachineName = perfCounter.MachineName,
TimeStarted = timeStarted,
TimeFinished = timeFinished,
PerformanceData = string.Join(",", dataValues),
UserID = currUser[0].UserID
};
this.CreateLog(log);
logIt.Add(new LogInfo
{
LogName = logName,
CounterName = perfCounter.CounterName,
InstanceName = perfCounter.InstanceName,
CategoryName = perfCounter.CategoryName,
MachineName = perfCounter.MachineName,
TimeStarted = timeStarted,
TimeFinished = timeFinished,
PerformanceData = dataValues.ToList<string>()
});
break;
}
}
}
else
{
PerformanceCounter perfCounter = new PerformanceCounter(categoryName, counterName,
"", theMac[0].MachineName);
string data = "";
List<UserInfo> currUser = this.GetUserByName(WindowsIdentity.GetCurrent().Name);
string timeStarted = DateTime.Now.ToString("MM/dd/yyyy - h:mm:ss tt");
//string[] dataValues = new string[(int)seconds];
List<string> dataValues = new List<string>();
var hubConnection = new HubConnection("http://localhost/PerfMon2/");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
var perfMon = hubConnection.CreateProxy("LiveHub");
// perfMon.On("sendValue", message => Console.WriteLine(message));
perfMon.On("showValue", json => Console.WriteLine(json));
hubConnection.Start().Wait();
List<DataValueInfo> lol = new List<DataValueInfo>();
for (int i = 0; i < seconds; i++)
{
data = "Value " + i + ": " + perfCounter.NextValue().ToString();
//dataValues[i] = data;
dataValues.Add(data);
lol.Add(new DataValueInfo
{
Value = perfCounter.NextValue().ToString()
});
// perfMon.Invoke<List<DataValueInfo>>("Send", lol);
perfMon.Invoke("SendToClient", lol);
Thread.Sleep(1000);
}
string timeFinished = DateTime.Now.ToString("MM/dd/yyyy - h:mm:ss tt");
Log log = new Log
{
LogName = logName,
CounterName = perfCounter.CounterName,
InstanceName = perfCounter.InstanceName,
CategoryName = perfCounter.CategoryName,
MachineName = perfCounter.MachineName,
TimeStarted = timeStarted,
TimeFinished = timeFinished,
PerformanceData = string.Join(",", dataValues),
UserID = currUser[0].UserID
};
this.CreateLog(log);
logIt.Add(new LogInfo
{
LogName = logName,
CounterName = perfCounter.CounterName,
InstanceName = perfCounter.InstanceName,
CategoryName = perfCounter.CategoryName,
MachineName = perfCounter.MachineName,
TimeStarted = timeStarted,
TimeFinished = timeFinished,
PerformanceData = dataValues.ToList<string>()
});
}
return logIt;
}
Here is the controller for the method in LogController.cs :
[AcceptVerbs("GET", "POST")]
public List<LogInfo> Log_Perf_Data(string machine_name, string category_name, string counter_name, string instance_name,
string log_name, long? seconds, string live, string enforceQuery)
{
LogController.CheckUser();
// POST api/log/post_data?machine_name=&category_name=&counter_name=&instance_name=&log_name=&seconds=
if (machine_name != null && category_name != null && counter_name != null && log_name != null && seconds.HasValue && enforceQuery == null)
{
List<LogInfo> dataVal = logDB.LogTimedPerfData(machine_name, category_name, counter_name, instance_name,
log_name, live, seconds);
logDB.SaveChanges();
return dataVal;
}
return new List<LogInfo>();
}
Maybe you can implement it in push technique. Here is how I do it:
Class with message
public class Message
{
/// <summary>
/// The name who will receive this message.
/// </summary>
public string RecipientName { get; set; }
/// <summary>
/// The message content.
/// </summary>
public string MessageContent { get; set; }
}
Class that will represent client:
public class Client
{
private ManualResetEvent messageEvent = new ManualResetEvent(false);
private Queue<Message> messageQueue = new Queue<Message>();
/// <summary>
/// This method is called by a sender to send a message to this client.
/// </summary>
/// <param name="message">the new message</param>
public void EnqueueMessage(Message message)
{
lock (messageQueue)
{
messageQueue.Enqueue(message);
// Set a new message event.
messageEvent.Set();
}
}
/// <summary>
/// This method is called by the client to receive messages from the message queue.
/// If no message, it will wait until a new message is inserted.
/// </summary>
/// <returns>the unread message</returns>
public Message DequeueMessage()
{
// Wait until a new message.
messageEvent.WaitOne();
lock (messageQueue)
{
if (messageQueue.Count == 1)
{
messageEvent.Reset();
}
return messageQueue.Dequeue();
}
}
}
Class to send messages to clients:
public class ClientAdapter
{
/// <summary>
/// The recipient list.
/// </summary>
private Dictionary<string, Client> recipients = new Dictionary<string,Client>();
/// <summary>
/// Send a message to a particular recipient.
/// </summary>
public void SendMessage(Message message)
{
if (recipients.ContainsKey(message.RecipientName))
{
Client client = recipients[message.RecipientName];
client.EnqueueMessage(message);
}
}
/// <summary>
/// Called by a individual recipient to wait and receive a message.
/// </summary>
/// <returns>The message content</returns>
public string GetMessage(string userName)
{
string messageContent = string.Empty;
if (recipients.ContainsKey(userName))
{
Client client = recipients[userName];
messageContent = client.DequeueMessage().MessageContent;
}
return messageContent;
}
/// <summary>
/// Join a user to the recipient list.
/// </summary>
public void Join(string userName)
{
recipients[userName] = new Client();
}
/// <summary>
/// Singleton pattern.
/// This pattern will ensure there is only one instance of this class in the system.
/// </summary>
public static ClientAdapter Instance = new ClientAdapter();
private ClientAdapter() { }
}
Sending messages:
Message message = new Message
{
RecipientName = tbRecipientName.Text.Trim(),
MessageContent = tbMessageContent.Text.Trim()
};
if (!string.IsNullOrWhiteSpace(message.RecipientName) && !string.IsNullOrEmpty(message.MessageContent))
{
// Call the client adapter to send the message to the particular recipient instantly.
ClientAdapter.Instance.SendMessage(message);
}
Receive messages (this is JavaScript functions written in test page. They render content of the message on ASPX page. Here you should implement your logic):
// This method will persist a http request and wait for messages.
function waitEvent() {
CSASPNETReverseAJAX.Dispatcher.WaitMessage("<%= Session["userName"] %>",
function (result) {
displayMessage(result);
// Keep looping.
setTimeout(waitEvent, 0);
}, function () {
// Keep looping.
setTimeout(waitEvent, 0);
});
}
// Append a message content to the result panel.
function displayMessage(message) {
var panel = document.getElementById("<%= lbMessages.ClientID %>");
panel.innerHTML += currentTime() + ": " + message + "<br />";
}
// Return a current time string.
function currentTime() {
var currentDate = new Date();
return currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
}

How to efficiently remove a query string by Key from a Url?

How to remove a query string by Key from a Url?
I have the below method which works fine but just wondering is there any better/shorter way? or a built-in .NET method which can do it more efficiently?
public static string RemoveQueryStringByKey(string url, string key)
{
var indexOfQuestionMark = url.IndexOf("?");
if (indexOfQuestionMark == -1)
{
return url;
}
var result = url.Substring(0, indexOfQuestionMark);
var queryStrings = url.Substring(indexOfQuestionMark + 1);
var queryStringParts = queryStrings.Split(new [] {'&'});
var isFirstAdded = false;
for (int index = 0; index <queryStringParts.Length; index++)
{
var keyValue = queryStringParts[index].Split(new char[] { '=' });
if (keyValue[0] == key)
{
continue;
}
if (!isFirstAdded)
{
result += "?";
isFirstAdded = true;
}
else
{
result += "&";
}
result += queryStringParts[index];
}
return result;
}
For example I can call it like:
Console.WriteLine(RemoveQueryStringByKey(#"http://www.domain.com/uk_pa/PostDetail.aspx?hello=hi&xpid=4578", "xpid"));
Hope the question is clear.
Thanks,
This works well:
public static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url);
// this gets all the query string key value pairs as a collection
var newQueryString = HttpUtility.ParseQueryString(uri.Query);
// this removes the key if exists
newQueryString.Remove(key);
// this gets the page path from root without QueryString
string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path);
return newQueryString.Count > 0
? String.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
: pagePathWithoutQueryString;
}
an example:
RemoveQueryStringByKey("https://www.google.co.uk/search?#hl=en&output=search&sclient=psy-ab&q=cookie", "q");
and returns:
https://www.google.co.uk/search?#hl=en&output=search&sclient=psy-ab
var queryString = "hello=hi&xpid=4578";
var qs = System.Web.HttpUtility.ParseQueryString(queryString);
qs.Remove("xpid");
var newQuerystring = qs.ToString();
This still works in .NET 5.
There's a useful class called UriBuilder in the System namespace. We can use it along with a couple of extension methods to do the following:
Uri u = new Uri("http://example.com?key1=value1&key2=value2");
u = u.DropQueryItem("key1");
Or like this:
Uri u = new Uri("http://example.com?key1=value1&key2=value2");
UriBuilder b = new UriBuilder(u);
b.RemoveQueryItem("key1");
u = b.Uri;
The extension methods:
using System;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
public static class UriExtensions
{
public static Uri DropQueryItem(this Uri u, string key)
{
UriBuilder b = new UriBuilder(u);
b.RemoveQueryItem(key);
return b.Uri;
}
}
public static class UriBuilderExtensions
{
private static string _ParseQueryPattern = #"(?<key>[^&=]+)={0,1}(?<value>[^&]*)";
private static Regex _ParseQueryRegex = null;
private static Regex ParseQueryRegex
{
get
{
if (_ParseQueryRegex == null)
{
_ParseQueryRegex = new Regex(_ParseQueryPattern, RegexOptions.Compiled | RegexOptions.Singleline);
}
return _ParseQueryRegex;
}
}
public static void SetQueryItem(this UriBuilder b, string key, string value)
{
NameValueCollection parms = ParseQueryString(b.Query);
parms[key] = value;
b.Query = RenderQuery(parms);
}
public static void RemoveQueryItem(this UriBuilder b, string key)
{
NameValueCollection parms = ParseQueryString(b.Query);
parms.Remove(key);
b.Query = RenderQuery(parms);
}
private static string RenderQuery(NameValueCollection parms)
{
StringBuilder sb = new StringBuilder();
for (int i=0; i<parms.Count; i++)
{
string key = parms.Keys[i];
sb.Append(key + "=" + parms[key]);
if (i < parms.Count - 1)
{
sb.Append("&");
}
}
return sb.ToString();
}
public static NameValueCollection ParseQueryString(string query, bool caseSensitive = true)
{
NameValueCollection pairs = new NameValueCollection(caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase);
string q = query.Trim().TrimStart(new char[] {'?'});
MatchCollection matches = ParseQueryRegex.Matches(q);
foreach (Match m in matches)
{
string key = m.Groups["key"].Value;
string value = m.Groups["value"].Value;
if (pairs[key] != null)
{
pairs[key] = pairs[key] + "," + value;
}
else
{
pairs[key] = value;
}
}
return pairs;
}
}
I know this is a rather old question, but everything I read felt a bit complicated.
public Uri GetUriWithoutQueryParam( Uri originalUri, string paramKey ) {
NameValueCollection newQuery = HttpUtility.ParseQueryString( originalUri.Query );
newQuery.Remove( paramKey );
return new UriBuilder( originalUri ) { Query = newQuery.ToString() }.Uri;
}
We can also do it using regex
string queryString = "Default.aspx?Agent=10&Language=2"; //Request.QueryString.ToString();
string parameterToRemove="Language"; //parameter which we want to remove
string regex=string.Format("(&{0}=[^&\s]+|(?<=\?){0}=[^&\s]+&?)",parameterToRemove); //this will not work for javascript, for javascript you can do following
string finalQS = Regex.Replace(queryString, regex, "");
//javascript(following is not js syntex, just want to give idea how we can able do it in js)
string regex1 = string.Format("(&{0}=[^&\s]+)",parameterToRemove);
string regex2 = string.Format("(\?{0}=[^&\s]+&?)",parameterToRemove);
string finalQS = Regex.Replace(queryString, regex1, "").Replace(queryString, regex2, "");
https://regexr.com/3i9vj
How about this:
string RemoveQueryStringByKey(string url, string key)
{
string ret = string.Empty;
int index = url.IndexOf(key);
if (index > -1)
{
string post = string.Empty;
// Find end of key's value
int endIndex = url.IndexOf('&', index);
if (endIndex != -1) // Last query string value?
{
post = url.Substring(endIndex, url.Length - endIndex);
}
// Decrement for ? or & character
--index;
ret = url.Substring(0, index) + post;
}
return ret;
}
I found a way without using Regex:
private string RemoveQueryStringByKey(string sURL, string sKey) {
string sOutput = string.Empty;
int iQuestion = sURL.IndexOf('?');
if (iQuestion == -1) return (sURL);
int iKey = sURL.Substring(iQuestion).IndexOf(sKey) + iQuestion;
if (iKey == -1) return (sURL);
int iNextAnd = sURL.Substring(iKey).IndexOf('&') + iKey + 1;
if (iNextAnd == -1) {
sOutput = sURL.Substring(0, iKey - 1);
}
else {
sOutput = sURL.Remove(iKey, iNextAnd - iKey);
}
return (sOutput);
}
I did try this with adding another field at the end, and it works fine for that too.
I'm thinking the shortest way (that I believe produces a valid URL in all cases, assuming the URL was valid to begin with) would be to use this regex (where getRidOf is the variable name you are trying to remove) and the replacement is a zero-length string ""):
(?<=[?&])getRidOf=[^&]*(&|$)
or maybe even
\bgetRidOf=[^&]*(&|$)
while possibly not the absolute prettiest URLs, I think they are all valid:
INPUT OUTPUT
----------- ------------
blah.com/blah.php?getRidOf=d.co&blah=foo blah.com/blah.php?blah=foo
blah.com/blah.php?f=0&getRidOf=d.co&blah=foo blah.com/blah.php?f=0&blah=foo
blah.com/blah.php?hello=true&getRidOf=d.co blah.com/blah.php?hello=true&
blah.com/blah.php?getRidOf=d.co blah.com/blah.php?
and it's a simple regex replace:
Dim RegexObj as Regex = New Regex("(?<=[?&])getRidOf=[^&]*(&|$)")
RegexObj.Replace("source.url.com/find.htm?replace=true&getRidOf=PLEASE!!!", "")
...should result in the string:
"source.url.com/find.htm?replace=true&"
...which seems to be valid for an ASP.Net application, while replace does equal true (not true& or anything like that)
I'll try to adapt it if you have a case where it won't work :)
public static string RemoveQueryStringByKey(string sURL, string sKey)
{
string sOutput = string.Empty;
string sToReplace = string.Empty;
int iFindTheKey = sURL.IndexOf(sKey);
if (iFindTheKey == -1) return (sURL);
int iQuestion = sURL.IndexOf('?');
if (iQuestion == -1) return (sURL);
string sEverythingBehindQ = sURL.Substring(iQuestion);
List<string> everythingBehindQ = new List<string>(sEverythingBehindQ.Split('&'));
foreach (string OneParamPair in everythingBehindQ)
{
int iIsKeyInThisParamPair = OneParamPair.IndexOf(sKey);
if (iIsKeyInThisParamPair != -1)
{
sToReplace = "&" + OneParamPair;
}
}
sOutput = sURL.Replace(sToReplace, "");
return (sOutput);
}
Below code before deleting your QueryString.
PropertyInfo isreadonly =
typeof(System.Collections.Specialized.NameValueCollection).GetProperty(
"IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
// make collection editable
isreadonly.SetValue(this.Request.QueryString, false, null);
// remove
this.Request.QueryString.Remove("yourKey");
Sorry this is a bit dirty but should work in older framework
public String RemoveQueryString( String rawUrl , String keyName)
{
var currentURL_Split = rawUrl.Split('&').ToList();
currentURL_Split = currentURL_Split.Where(o => !o.ToLower().StartsWith(keyName.ToLower()+"=")).ToList();
String New_RemovedKey = String.Join("&", currentURL_Split.ToArray());
New_RemovedKey = New_RemovedKey.Replace("&&", "&");
return New_RemovedKey;
}
Here is my solution:
I'v added some extra input validation.
public static void TryRemoveQueryStringByKey(ref string url, string key)
{
if (string.IsNullOrEmpty(url) ||
string.IsNullOrEmpty(key) ||
Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false)
{
return false;
}
try
{
Uri uri = new Uri(url);
// This gets all the query string key value pairs as a collection
NameValueCollection queryCollection = HttpUtility.ParseQueryString(uri.Query);
string keyValue = queryCollection.Get(key);
if (url.IndexOf("&" + key + "=" + keyValue, StringComparison.OrdinalIgnoreCase) >= 0)
{
url = url.Replace("&" + key + "=" + keyValue, String.Empty);
return true;
}
else if (url.IndexOf("?" + key + "=" + keyValue, StringComparison.OrdinalIgnoreCase) >= 0)
{
url = url.Replace("?" + key + "=" + keyValue, String.Empty);
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
Some unit testing examples:
string url1 = "http://www.gmail.com?a=1&cookie=cookieValue"
Assert.IsTrue(TryRemoveQueryStringByKey(ref url1,"cookie")); //OUTPUT: "http://www.gmail.com?a=1"
string url2 = "http://www.gmail.com?cookie=cookieValue"
Assert.IsTrue(TryRemoveQueryStringByKey(ref url2,"cookie")); //OUTPUT: "http://www.gmail.com"
string url3 = "http://www.gmail.com?cookie="
Assert.IsTrue(TryRemoveQueryStringByKey(ref url2,"cookie")); //OUTPUT: "http://www.gmail.com"
Here's a full solution that works with >= 0 params specified, and any form of URL:
/// <summary>
/// Given a URL in any format, return URL with specified query string param removed if it exists
/// </summary>
public static string StripQueryStringParam(string url, string paramToRemove)
{
return StripQueryStringParams(url, new List<string> {paramToRemove});
}
/// <summary>
/// Given a URL in any format, return URL with specified query string params removed if it exists
/// </summary>
public static string StripQueryStringParams(string url, List<string> paramsToRemove)
{
if (paramsToRemove == null || !paramsToRemove.Any()) return url;
var splitUrl = url.Split('?');
if (splitUrl.Length == 1) return url;
var urlFirstPart = splitUrl[0];
var urlSecondPart = splitUrl[1];
// Even though in most cases # isn't available to context,
// we may be passing it in explicitly for helper urls
var secondPartSplit = urlSecondPart.Split('#');
var querystring = secondPartSplit[0];
var hashUrlPart = string.Empty;
if (secondPartSplit.Length > 1)
{
hashUrlPart = "#" + secondPartSplit[1];
}
var nvc = HttpUtility.ParseQueryString(querystring);
if (!nvc.HasKeys()) return url;
// Remove any matches
foreach (var key in nvc.AllKeys)
{
if (paramsToRemove.Contains(key))
{
nvc.Remove(key);
}
}
if (!nvc.HasKeys()) return urlFirstPart;
return urlFirstPart +
"?" + string.Join("&", nvc.AllKeys.Select(c => c.ToString() + "=" + nvc[c.ToString()])) +
hashUrlPart;
}
A more modern answer for this old question in case someone else stumbles across it like I did.
This is using the Uri class to parse the URL (can be skipped if your URL is already in a Uri object) and LINQ to filter the query string.
public static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url, UriKind.Absolute);
var queryParts = uri.Query
.TrimStart('?')
.Split('&')
.Where(item => string.CompareOrdinal(item, key) != 0);
return uri.Scheme + Uri.SchemeDelimiter
+ uri.Authority
+ uri.AbsolutePath
+ "?" + string.Join("&", queryParts);
}
With reusing the signature from the accepted answer, but preserving the fragment and using QueryHelpers from Microsoft.AspNetCore.WebUtilities.
public static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url);
var newQueryString = QueryHelpers.ParseQuery(uri.Query);
if (newQueryString.Remove(key))
{
var urlWithNewQuery = QueryHelpers.AddQueryString(
uri.GetLeftPart(UriPartial.Path),
newQueryString.ToDictionary(
queryParam => queryParam.Key,
queryParam => queryParam.Value.ToString()))
return $"{urlWithNewQuery}{uri.Fragment}";
}
return url;
}
string url = HttpContext.Current.Request.Url.AbsoluteUri;
string[] separateURL = url.Split('?');
NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(separateURL[1]);
queryString.Remove("param_toremove");
string revisedurl = separateURL[0] + "?" + queryString.ToString();

Can I expand a string that contains C# literal expressions at runtime

If I have a string that contains a c# string literal expression can I "expand" it at runtime
public void TestEvaluateString()
{
string Dummy = EvalString( #"Contains \r\n new line");
Debug.Assert(Dummy == "Contains \r\n new line");
}
private string EvalString(string input)
{
return "Contains \r\n new line";
}
Like Can I convert a C# string value to an escaped string literal, but in reverse?
Similar to Mikael answer but using the CSharpCodeProvider:
public static string ParseString(string txt)
{
var provider = new Microsoft.CSharp.CSharpCodeProvider();
var prms = new System.CodeDom.Compiler.CompilerParameters();
prms.GenerateExecutable = false;
prms.GenerateInMemory = true;
var results = provider.CompileAssemblyFromSource(prms, #"
namespace tmp
{
public class tmpClass
{
public static string GetValue()
{
return " + "\"" + txt + "\"" + #";
}
}
}");
System.Reflection.Assembly ass = results.CompiledAssembly;
var method = ass.GetType("tmp.tmpClass").GetMethod("GetValue");
return method.Invoke(null, null) as string;
}
You might be better off using a dictionary of wildcards and just replacing them in the string.
Regex.Unescape would be my method of choice.
Not sure if this is the simplest way, but by referencing the Microsoft.JScript namespace you can reparse it with the javascript eval function.
Here's a test for the code at the bottom
var evalToString = Evaluator.MyStr("test \\r\\n test");
This will turn the \r into a carriage return.
And the implementation
public class Evaluator
{
public static object MyStr(string statement)
{
return _evaluatorType.InvokeMember(
"MyStr",
BindingFlags.InvokeMethod,
null,
_evaluator,
new object[] { statement }
);
}
static Evaluator()
{
ICodeCompiler compiler;
compiler = new JScriptCodeProvider().CreateCompiler();
CompilerParameters parameters;
parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
CompilerResults results;
results = compiler.CompileAssemblyFromSource(parameters, _jscriptSource);
Assembly assembly = results.CompiledAssembly;
_evaluatorType = assembly.GetType("Evaluator.Evaluator");
_evaluator = Activator.CreateInstance(_evaluatorType);
}
private static object _evaluator = null;
private static Type _evaluatorType = null;
private static readonly string _jscriptSource =
#"package Evaluator
{
class Evaluator
{
public function MyStr(expr : String) : String
{
var x;
eval(""x='""+expr+""';"");
return x;
}
}
}";
}
If you're just looking to do "simple" escape characters as defined on the Microsoft site, you can use this routine and save importing external libs:
public static class StringExtensions
{
/* https://msdn.microsoft.com/en-us/library/aa691087(v=vs.71).aspx */
private readonly static SortedDictionary<char, char> EscapeMap = new SortedDictionary<char, char>
{
{ '\'', '\'' },
{ '"', '\"' },
{ '\\', '\\' },
{ '0', '\0' },
{ 'a', '\a' },
{ 'b', '\b' },
{ 'f', '\f' },
{ 'n', '\n' },
{ 'r', '\r' },
{ 't', '\t' },
{ 'v', '\v' },
};
public static string UnescapeSimple(this string escaped)
{
if (escaped == null)
return escaped;
var sb = new StringBuilder();
bool inEscape = false;
var s = 0;
for (var i = 0; i < escaped.Length; i++)
{
if (!inEscape && escaped[i] == '\\')
{
inEscape = true;
continue;
}
if (inEscape)
{
char mapChar;
if (EscapeMap.TryGetValue(escaped[i], out mapChar))
{
sb.Append(escaped.Substring(s, i - s - 1));
sb.Append(mapChar);
s = i + 1;
}
inEscape = false;
}
}
sb.Append(escaped.Substring(s));
return sb.ToString();
}
}
Here's a unit test to prove it:
[TestMethod]
public void UnescapeSimpleTest()
{
var noEscapes = #"This is a test".UnescapeSimple();
Assert.AreEqual("This is a test", noEscapes, nameof(noEscapes));
var singleEscape = #"\n".UnescapeSimple();
Assert.AreEqual("\n", singleEscape, nameof(singleEscape));
var allEscape = #"\'\""\\\0\a\b\f\n\r\t\v".UnescapeSimple();
Assert.AreEqual("\'\"\\\0\a\b\f\n\r\t\v", allEscape, nameof(allEscape));
var textInEscapes = #"\tthis\n\ris\\a\ntest".UnescapeSimple();
Assert.AreEqual("\tthis\n\ris\\a\ntest", textInEscapes, nameof(textInEscapes));
var backslashNoEscapes = #"\,\h\qtest".UnescapeSimple();
Assert.AreEqual(#"\,\h\qtest", backslashNoEscapes, nameof(backslashNoEscapes));
var emptyStr = "".UnescapeSimple();
Assert.AreEqual("", emptyStr, nameof(emptyStr));
// Prove Enviroment.NewLine is "\r\n" and not "\n\r" (Windows PC)
var newLine = #"\r\n".UnescapeSimple();
Assert.AreEqual(Environment.NewLine, newLine, nameof(newLine));
// Double check prior test (Windows PC)
var newLineWrong = #"\n\r".UnescapeSimple();
Assert.AreNotEqual(Environment.NewLine, newLineWrong, nameof(newLineWrong));
}
Feel free to tweak the EscapeMap or rename the function UnescapeSimple (awkward I know).
Note that this solution doesn't handle Unicode escape characters or hex or octal, it just handles the simple single character ones.
You can achieve this with a one-liner using the Microsoft.CodeAnalysis.CSharp.Scripting package.
private Task<string> EvaluateStringAsync(string input)
{
return CSharpScript.EvaluateAsync<string>('"' + input + '"');
}
If you start including the outer quotes in the method argument, the method can be generalized to handle verbatim, interpolated, and concatenated strings too (.NET Fiddle):
private Task<string> EvaluateStringAsync(string input)
{
return CSharpScript.EvaluateAsync<string>(input);
}
// await EvaluateStringAsync(#"$""This is a number: {40:N3}""")
// Output: "This is a number: 40.000"
This method can be slow to invoke repeatedly. If you have a large number of strings to convert, you'd be better off batching them (.NET Fiddle):
private static Task<string[]> EvaluateStringsAsync(string[] inputs)
{
var inputsConcat = string.Concat(inputs.Select(x => $" {x},\r\n"));
var arrayInitializationCode = $"new[] {{\r\n{inputsConcat}}}";
return CSharpScript.EvaluateAsync<string[]>(arrayInitializationCode);
}
As with all dynamic compilation, you need to restrict your calls to trusted input only, or take measures to protect against injection attacks.

Categories

Resources