619 lines
20 KiB
C#
619 lines
20 KiB
C#
#define PROD_ // also change in LicenseFile.cs too, prevents compilation of the licensekey making bits
|
|
/*
|
|
* Created by SharpDevelop.
|
|
* User: D Macintosh
|
|
* Date: 10/25/2022
|
|
* Time: 9:32 PM
|
|
*
|
|
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Reflection;
|
|
|
|
namespace cdrtool
|
|
{
|
|
class cdrtool
|
|
{
|
|
public static string product = "CDR-Tool";
|
|
public static string version = "1.6.7";
|
|
|
|
public static int debug = 1;
|
|
public static int mode = 1;
|
|
public static int major_errs = 0;
|
|
public static int MAX_RETRY = 3;
|
|
public static int MIN_INT = 10; // minimum interval for timer in minutes
|
|
public static int MAX_INT = 360; // maximum interval for timer
|
|
|
|
public static int webMaxWait = 20; //max wait time in minutes for webaccess
|
|
|
|
public static bool logappend = true;
|
|
public static string logname = "logfile.txt";
|
|
public static string licfile = "license.txt";
|
|
public static StreamWriter outfile;
|
|
|
|
public static string username;
|
|
public static string password;
|
|
public static string company;
|
|
public static bool licensed;
|
|
|
|
public static string dirpath = "";
|
|
public static string token = "";
|
|
|
|
public static int begin = 8;
|
|
public static int end = 20;
|
|
public static int interval = 0;
|
|
public static bool checkPrevMonth = true;
|
|
public static int prevMonOffset = 1;
|
|
|
|
public static string mfa = ""; // 6-digit MFA code
|
|
public static int mfa_delay = 120; // wait time for email to arrive in seconds
|
|
|
|
public static string ssh_user;
|
|
public static string ssh_pass;
|
|
public static string ssh_host;
|
|
public static string ssh_path = "." + Path.DirectorySeparatorChar ;
|
|
public static int ssh_port = 22;
|
|
public static bool sftp = false;
|
|
|
|
public static StringBuilder adminLog = new StringBuilder();
|
|
public static StringBuilder auditLog = new StringBuilder();
|
|
|
|
public static int adlvl = 2;
|
|
|
|
/* see https://denhamcoder.net/2018/08/25/embedding-net-assemblies-inside-net-assemblies/ */ // still needs external copy during compilation!
|
|
// Loads NewtonsoftJson lib into memory as .dll
|
|
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
|
{
|
|
string name = new AssemblyName(args.Name).Name;
|
|
string embname = "other";
|
|
|
|
if (name == "BouncyCastle.Cryptography")
|
|
{
|
|
embname = "CDRTool.Embedded.BouncyCastle.Cryptography.dll";
|
|
}
|
|
else if (name == "Google.Apis")
|
|
{
|
|
embname = "CDRTool.Embedded.Google.Apis.dll";
|
|
}
|
|
else if (name == "Google.Apis.Auth")
|
|
{
|
|
embname = "CDRTool.Embedded.Google.Apis.Auth.dll";
|
|
}
|
|
else if (name == "Google.Apis.Core")
|
|
{
|
|
embname = "CDRTool.Embedded.Google.Apis.Core.dll";
|
|
}
|
|
else if (name == "Google.Apis.Gmail.v1")
|
|
{
|
|
embname = "CDRTool.Embedded.Google.Apis.Gmail.v1.dll";
|
|
}
|
|
else if (name == "Microsoft.Bcl.AsyncInterfaces")
|
|
{
|
|
embname = "CDRTool.Embedded.Microsoft.Bcl.AsyncInterfaces.dll";
|
|
}
|
|
else if (name == "Microsoft.Extensions.DependencyInjection.Abstractions")
|
|
{
|
|
embname = "CDRTool.Embedded.Microsoft.Extensions.DependencyInjection.Abstractions.dll";
|
|
}
|
|
else if (name == "Microsoft.Extensions.Logging.Abstractions")
|
|
{
|
|
embname = "CDRTool.Embedded.Microsoft.Extensions.Logging.Abstractions.dll";
|
|
}
|
|
else if (name == "Newtonsoft.Json")
|
|
{
|
|
embname = "CDRTool.Embedded.Newtonsoft.Json.dll";
|
|
}
|
|
else if (name == "Renci.SshNet")
|
|
{
|
|
embname = "CDRTool.Embedded.Renci.SshNet.dll";
|
|
}
|
|
else if (name == "System.Buffers")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Buffers.dll";
|
|
}
|
|
else if (name == "System.CodeDom")
|
|
{
|
|
embname = "CDRTool.Embedded.System.CodeDom.dll";
|
|
}
|
|
else if (name == "System.Diagnostics.DiagnosticSource")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Diagnostics.DiagnosticSource.dll";
|
|
}
|
|
else if (name == "System.Formats.Asn1")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Formats.Asn1.dll";
|
|
}
|
|
else if (name == "System.Memory")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Memory.dll";
|
|
}
|
|
else if (name == "System.Numerics.Vectors")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Numerics.Vectors.dll";
|
|
}
|
|
else if (name == "System.Runtime.CompilerServices.Unsafe")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Runtime.CompilerServices.Unsafe.dll";
|
|
}
|
|
else if (name == "System.Threading.Tasks.Extensions")
|
|
{
|
|
embname = "CDRTool.Embedded.System.Threading.Tasks.Extensions.dll";
|
|
}
|
|
else if (name == "System.ValueTuple")
|
|
{
|
|
embname = "CDRTool.Embedded.System.ValueTuple.dll";
|
|
}
|
|
else
|
|
{
|
|
embname = "something_bad";
|
|
}
|
|
|
|
//Console.WriteLine("returning assy: " + name);
|
|
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(embname))
|
|
{
|
|
var assemblyData = new Byte[stream.Length];
|
|
stream.Read(assemblyData, 0, assemblyData.Length);
|
|
return Assembly.Load(assemblyData);
|
|
}
|
|
}
|
|
|
|
|
|
public static void Main(string[] args)
|
|
{
|
|
// Load Embedded Resources
|
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
|
|
//foreach (var resource in Assembly.GetExecutingAssembly().GetManifestResourceNames()) Console.WriteLine("Resource: " + resource);
|
|
|
|
Main2(args);
|
|
}
|
|
|
|
public static async void Main2(string[] args)
|
|
{
|
|
#if !PROD_
|
|
// LicenseFile.makeLicenseFile(licfile, "Support", 5, false);
|
|
LicenseFile.makeLicenseFile("license.txt", "Support", 1, true);
|
|
Console.WriteLine(LicenseFile.validate("license.txt"));
|
|
return;
|
|
#endif
|
|
// Clears 'async' compiler error
|
|
await System.Threading.Tasks.Task.CompletedTask;
|
|
|
|
// Enforce TLS1.2 Sep2017
|
|
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072 | (SecurityProtocolType)768 | (SecurityProtocolType)192;
|
|
|
|
// Logging file
|
|
outfile = new StreamWriter(@""+logname,logappend);
|
|
outfile.AutoFlush = true;
|
|
|
|
string ban2 = product+" v"+version+" (c) 2022-2026 Sommet Consulting Inc.";
|
|
ban2 = "\u2551 " + ban2 + " \u2551";
|
|
string ban1 = "\u2554";
|
|
for ( int i = 2; i < ban2.Length; i++) { ban1 += "\u2550"; }
|
|
ban1 += "\u2557";
|
|
string ban3 = "\u255a";
|
|
for ( int i = 2; i < ban2.Length; i++) { ban3 += "\u2550"; }
|
|
ban3 += "\u255d";
|
|
Logger.Log(ban1); Logger.Log(ban2); Logger.Log(ban3);
|
|
//Logger.Log("--- UNLICENSED, FOR EVALUATION USE ONLY ---");
|
|
dynamic license;
|
|
|
|
Arguments myargs = new Arguments(args);
|
|
|
|
CookieContainer cookiejar = new CookieContainer();
|
|
|
|
if ( (myargs.Count < 1 ) || myargs.Exists("help") || myargs.Exists("h") )
|
|
{
|
|
Logger.Log(" usage: " + System.Diagnostics.Process.GetCurrentProcess().ProcessName + " [-options]");
|
|
//Console.WriteLine("retry: -retries n");
|
|
//Logger.Log("--- UNLICENSED, FOR EVALUATION USE ONLY ---");
|
|
Logger.Log(" login: -u user -p passwd");
|
|
Logger.Log(" time: -begin 8 -end 20 -interval 60");
|
|
Logger.Log(" path: -path " + ( Path.DirectorySeparatorChar.ToString()=="\\" ? "C:" : "") + "/directory/subdir");
|
|
Logger.Log(" sftp: -sh sftp.server -su user -sp passwd -sport 22 -spath CustomPath");
|
|
|
|
//Logger.Log(" debug: -debug n");
|
|
//Console.WriteLine("-fiddler"); // not needed with Fiddler v5.x
|
|
return;
|
|
}
|
|
|
|
WebProxy proxyObject = null;
|
|
#if !PROD_
|
|
{
|
|
//cdrtool Creds
|
|
if (myargs.Exists("dm"))
|
|
{
|
|
username = "Doug.Macintosh@rci.rogers.com";
|
|
password = "Rogers1!";
|
|
|
|
//clientInfo = "logging in as Doug Macintosh";
|
|
}
|
|
|
|
if (myargs.IsTrue("fiddler"))
|
|
{
|
|
Logger.Log("Enabling Fiddler Proxy");
|
|
proxyObject = new WebProxy("http://127.0.0.1:8888/"); // Fiddler proxy
|
|
|
|
//System.Net.WebRequest.DefaultWebProxy = new System.Net.WebProxy("127.0.0.1", 8888);
|
|
}
|
|
//}
|
|
#endif
|
|
if (myargs.Exists("debug"))
|
|
{
|
|
debug = int.Parse(myargs.Single("debug"));
|
|
Logger.Log(5, "Setting debug level to {0}", debug);
|
|
}
|
|
//}
|
|
|
|
|
|
if (myargs.Exists("path"))
|
|
{
|
|
dirpath = myargs.Single("path");
|
|
|
|
}
|
|
else
|
|
{
|
|
dirpath = "." + Path.DirectorySeparatorChar.ToString();
|
|
}
|
|
|
|
if (dirpath.EndsWith(Path.DirectorySeparatorChar.ToString())) { } else { dirpath += Path.DirectorySeparatorChar.ToString();}
|
|
Logger.Log(0,"Setting filestore path to {0}", dirpath);
|
|
|
|
int b = begin;
|
|
int e = end;
|
|
if (myargs.Exists("begin"))
|
|
{
|
|
b = int.Parse(myargs.Single("begin"));
|
|
}
|
|
if (myargs.Exists("end"))
|
|
{
|
|
e = int.Parse(myargs.Single("end"));
|
|
}
|
|
if ( ( e > b) && ( ( e > 0) && ( e <= 24 )) )
|
|
{
|
|
end = e;
|
|
}
|
|
else
|
|
{
|
|
Logger.Log(0,"Invalid parameter for end hour. Must be 0-24h and greater than begin hour.");
|
|
return;
|
|
}
|
|
|
|
|
|
if ( ( b < e ) && ( ( b >= 0 ) && ( b < 24 ) ) )
|
|
{
|
|
begin = b;
|
|
}
|
|
else
|
|
{
|
|
Logger.Log(0,"Invalid parameter for start hour. Must be 0-24h and less than end hour.");
|
|
return;
|
|
}
|
|
|
|
if (myargs.Exists("interval"))
|
|
{
|
|
int i = int.Parse(myargs.Single("interval"));
|
|
if ( ( i >= MIN_INT ) && ( i <= MAX_INT ) )
|
|
{
|
|
interval = i;
|
|
}
|
|
else
|
|
{
|
|
Logger.Log(0,"Invalid parameter for interval in minutes. Must be 10-360min");
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
if (interval > 0)
|
|
{
|
|
Logger.Log(0,"Setting Timer Period {0}h to {1}h, {2}min interval", begin, end, interval);
|
|
}
|
|
else
|
|
{
|
|
Logger.Log(0,"Single Shot Mode. (set time: parameters to enable repetition).");
|
|
}
|
|
|
|
|
|
if (myargs.Exists("offset"))
|
|
{
|
|
prevMonOffset = int.Parse(myargs.Single("offset"));
|
|
Logger.Log(0, "Setting History File offset to -{0}", prevMonOffset);
|
|
}
|
|
|
|
|
|
|
|
if ((myargs.Exists("sh")) && (myargs.Exists("su")) && (myargs.Exists("sp")) )
|
|
{
|
|
ssh_host = myargs.Single("sh");
|
|
ssh_user = myargs.Single("su");
|
|
ssh_pass = myargs.Single("sp");
|
|
if (myargs.Exists("sport"))
|
|
{
|
|
ssh_port = int.Parse(myargs.Single("sport"));
|
|
}
|
|
|
|
if (myargs.Exists("spath"))
|
|
{
|
|
ssh_path = (myargs.Single("spath"));
|
|
}
|
|
|
|
sftp = true;
|
|
Logger.Log(0, "Uploading files as {3} to server: {0}:{1}{4}{2}", ssh_host, ssh_port, ssh_path, ssh_user, Path.DirectorySeparatorChar);
|
|
}
|
|
|
|
if ((myargs.Exists("u")) && (myargs.Exists("p")))
|
|
{
|
|
username = myargs.Single("u");
|
|
password = myargs.Single("p");
|
|
Logger.Log(0, "Setting authorized account to: {0}", username);
|
|
}
|
|
#if !PROD_
|
|
else if (myargs.Exists("dm"))
|
|
{
|
|
//already done
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
Logger.Log(0,"No valid login credentials found. Exiting.");
|
|
return;
|
|
}
|
|
|
|
// ***MAIN
|
|
//sftp.Upload_file("./CURRENT.csv", "10.0.0.3", 2222, "doug", "D4t4Guru");
|
|
//return;
|
|
|
|
do {
|
|
|
|
var now = DateTime.Now;
|
|
var start = new DateTime(now.Year, now.Month, now.Day, begin, 0, 0, now.Kind);
|
|
|
|
// Logger.Log(5," day of week: {0}", now.DayOfWeek);
|
|
TimeSpan span = now - start; // - TimeSpan.FromSeconds(1);
|
|
int m = (interval>0) ? interval - ((( span.Hours * 60 ) + span.Minutes ) % interval) : 0; //minutes to next logical interval
|
|
var nextTrigger = now + TimeSpan.FromMinutes(m>0 ? m:interval) - TimeSpan.FromSeconds(span.Seconds); // sync to next logical interval
|
|
Logger.Log(5," next event at: {0}",nextTrigger);
|
|
|
|
if( ( interval == 0 ) || ( now.Hour >= begin && now.Hour <= end))
|
|
{
|
|
// Work loop
|
|
bool LoggedIn = false;
|
|
int tries = 1;
|
|
|
|
//Login
|
|
do
|
|
{
|
|
Logger.Log(0,"Initiating connection...");
|
|
tries++;
|
|
try
|
|
{
|
|
LoggedIn = WebRoutines.Login( username, password, ref token, ref cookiejar, proxyObject );
|
|
if (!LoggedIn) { Logger.Log(1," retrying {0}",tries); }
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Logger.Log("Error attempting to login to SIP Portal website");
|
|
|
|
if (!LoggedIn) { Logger.Log(1," retrying {0}",tries); }
|
|
}
|
|
|
|
}
|
|
while (!LoggedIn && (tries <= MAX_RETRY) );
|
|
|
|
if ( !LoggedIn )
|
|
{
|
|
Logger.Log("Aborting due to inability to login to SIP Portal website");
|
|
break; // DWMM Jan26
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
#if !PROD_
|
|
LicenseFile.makeLicenseFile(licfile, "Support", 5, false);
|
|
#endif
|
|
license = JObject.Parse(LicenseFile.validate(licfile));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("ERROR: Invalid License File!");
|
|
Logger.Log(5,ex.Message);
|
|
Logger.Log("Exiting.");
|
|
return;
|
|
}
|
|
string validity = (
|
|
((bool)license.Perpetual ? "Perpetual License: " : "License: ") + product + " has " +
|
|
((DateTime.Now <= (DateTime)license.ValidTo) ? (license.Type + " valid through " + license.ValidTo) :
|
|
((bool)license.Perpetual ? "Right to Use only." : "Expired on " + license.ValidTo)));
|
|
if (checkPrevMonth) Logger.Log(validity);
|
|
licensed = !validity.Contains("Expire");
|
|
if (licensed && checkPrevMonth) Logger.Log("For assistance please contact support@sommetconsulting.ca");
|
|
}
|
|
|
|
// Get Parameters
|
|
string wfr_guid = "";
|
|
string ch_guid = "";
|
|
string act_guid = "";
|
|
string co_guid = "";
|
|
string did_guid = "";
|
|
|
|
string year = "";
|
|
string mtd = "";
|
|
|
|
bool gotParams = false;
|
|
tries = 1;
|
|
|
|
do
|
|
{
|
|
try
|
|
{
|
|
if (licensed)
|
|
{
|
|
Logger.Log("Setting up...");
|
|
gotParams = WebRoutines.GetGUIDs(token, ref wfr_guid, ref ch_guid, ref act_guid, ref co_guid, ref did_guid,
|
|
ref year, ref mtd, ref cookiejar, proxyObject);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Error navigating SIP Portal");
|
|
Logger.Log(5, ex.Message);
|
|
if (!gotParams) { tries++; Logger.Log(0, " retrying {0}", tries); }
|
|
}
|
|
|
|
}
|
|
while (!gotParams && (tries < MAX_RETRY));
|
|
|
|
if (!gotParams)
|
|
{
|
|
Logger.Log("Aborting due to inability to navigate SIP Portal.");
|
|
break; //return;
|
|
}
|
|
|
|
// Get files
|
|
string filename = "CURRENT.csv";
|
|
//filename = ""; // use system filename
|
|
bool gotMTDFile = false;
|
|
tries = 1;
|
|
|
|
do
|
|
{
|
|
try
|
|
{
|
|
if (licensed)
|
|
{
|
|
gotMTDFile = WebRoutines.GetMTDFile(dirpath, filename, token, ref act_guid, ref co_guid, ref did_guid,
|
|
ref year, ref mtd, ref cookiejar, proxyObject);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Error attempting to download MTD File.");
|
|
Logger.Log(5, ex.Message);
|
|
if (!gotMTDFile) { tries++; Logger.Log(0, " retrying {0}", tries); }
|
|
}
|
|
|
|
}
|
|
while (!gotMTDFile && (tries < MAX_RETRY));
|
|
|
|
|
|
if (!gotMTDFile)
|
|
{
|
|
Logger.Log("Exiting due to inability to download Month-to-Date file.");
|
|
break; // return;
|
|
}
|
|
|
|
bool gotCDRHistoryFile = false;
|
|
tries = 1;
|
|
|
|
do
|
|
{
|
|
try
|
|
{
|
|
|
|
if (checkPrevMonth && licensed)
|
|
{
|
|
filename = ""; // use system filename
|
|
gotCDRHistoryFile = WebRoutines.GetCDRHistoryFile(dirpath, filename, prevMonOffset, token, ref act_guid, ref co_guid, ref did_guid,
|
|
ref year, ref mtd, ref cookiejar, proxyObject);
|
|
}
|
|
else
|
|
{
|
|
gotCDRHistoryFile = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Error attempting to download History File.");
|
|
Logger.Log(5, ex.Message);
|
|
if (!gotCDRHistoryFile) { tries++; Logger.Log(0, " retrying {0}", tries); }
|
|
}
|
|
|
|
}
|
|
while (!gotCDRHistoryFile && (tries < MAX_RETRY));
|
|
|
|
if (!gotCDRHistoryFile)
|
|
{
|
|
Logger.Log("Aborting due to inability to download CDR Monthly History file.");
|
|
break; // return;
|
|
}
|
|
|
|
checkPrevMonth = false;
|
|
|
|
Logger.Log(3,"Trigger hour {0} Now hour {1}", nextTrigger.Hour, now.Hour);
|
|
|
|
//if (( interval > 0) && ( nextTrigger.Hour <= ( now.Hour + 1)) ) // NEED TO HANDLE MIDNIGHT ROLLOVER STILL
|
|
if ( (interval > 0) ) // NEED TO HANDLE MIDNIGHT ROLLOVER STILL
|
|
{
|
|
Logger.Log(0,"Completed CDR processing. Next event at {0:00}h{1:00}", nextTrigger.Hour, nextTrigger.Minute);
|
|
}
|
|
else
|
|
{
|
|
Logger.Log(0,"Completed CDR processing.");
|
|
}
|
|
|
|
// LOG OUT
|
|
bool LoggedOut = WebRoutines.Logout(token, ref cookiejar, ref proxyObject);
|
|
|
|
//break; // exit while() loop we are using as in 'IF'
|
|
} //END OF WORK LOOP
|
|
|
|
now = DateTime.Now; // reset and clean up in case we started just before the next cycle was to start
|
|
|
|
// Moved stuff
|
|
span = now - start; // - TimeSpan.FromSeconds(1);
|
|
m = (interval > 0) ? interval - (((span.Hours * 60) + span.Minutes) % interval) : 0; //minutes to next logical interval
|
|
nextTrigger = now + TimeSpan.FromMinutes(m > 0 ? m : interval) - TimeSpan.FromSeconds(span.Seconds); // sync to next logical interval
|
|
//Logger.Log(0, " span {0} m:{1} next trigger {2})", span, m, nextTrigger);
|
|
|
|
if ( ( now.Hour < begin || now.Hour > end || nextTrigger.Hour < begin || nextTrigger.Hour > end) && ( interval > 0 ) )
|
|
{
|
|
nextTrigger = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0, now.Kind) ;
|
|
nextTrigger += TimeSpan.FromHours( (24 - now.Hour + begin) % 24 );
|
|
Logger.Log(0,"Going to sleep until {0:00}h{1:00}", nextTrigger.Hour, nextTrigger.Minute); // begin is always less than end in any given 24h cycle
|
|
checkPrevMonth = true;
|
|
}
|
|
|
|
if (interval > 0)
|
|
{
|
|
//now = DateTime.Now;
|
|
// if ((nextTrigger - now) < TimeSpan.FromSeconds(1)) now -= TimeSpan.FromSeconds(1);
|
|
//nextTrigger += TimeSpan.FromMinutes(interval);
|
|
//Logger.Log(3,"values adjusted: trig:{0} now:{1}", nextTrigger, now);
|
|
//Logger.Log(3, " thread sleeping {0}", (nextTrigger - now));
|
|
Logger.Log(5, " thread sleeping {0} ({1} - {2})", (nextTrigger - now), (nextTrigger), (now.ToString("HH:mm:ss.fff")));
|
|
token = ""; // reset auth token for session
|
|
Thread.Sleep(nextTrigger - now + TimeSpan.FromSeconds(0.5));
|
|
}
|
|
}
|
|
while ( ( licensed || checkPrevMonth) && (interval > 0));
|
|
|
|
} // end of MAIN()
|
|
|
|
} // cdrtool class
|
|
|
|
public static class IDictionaryExtensions
|
|
{
|
|
public static TKey FindKeyByValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TValue value)
|
|
{
|
|
if (dictionary == null)
|
|
throw new ArgumentNullException("dictionary");
|
|
|
|
foreach (KeyValuePair<TKey, TValue> pair in dictionary)
|
|
if (value.Equals(pair.Value)) return pair.Key;
|
|
|
|
throw new Exception("the value is not found in the dictionary");
|
|
}
|
|
}
|
|
|
|
} |