I have been able to create an application that (ab)uses TAPI3Lib and understands incoming calls, puts them on hold, rings an internal number, connects the line that is on hold with the target number.
Below is the code getting the caller number
private void tapi_ITTAPIEventNotification_Event_Event(TAPI3Lib.TAPI_EVENT TapiEvent, object pEvent)
{
try
{
switch (TapiEvent)
{
case TAPI3Lib.TAPI_EVENT.TE_CALLNOTIFICATION:
TAPI3Lib.ITCallNotificationEvent cln = pEvent as TAPI3Lib.ITCallNotificationEvent;
if (cln.Call.CallState == TAPI3Lib.CALL_STATE.CS_OFFERING)
{
string c = cln.Call.get_CallInfoString(TAPI3Lib.CALLINFO_STRING.CIS_CALLERIDNUMBER);
string target = cln.Call.Address.DialableAddress;
.
.
.
Here i run a MSSQL query to a database to identify who is the owner of the number. and i display a form on screen with the potential user that is calling. A button on the form allows you to ring internal numbers (this action automatically adds the current line on hold.
IEnumCall ec = ia[line].EnumerateCalls();
uint arg = 0;
ITCallInfo ici;
try
{
ec.Next(1, out ici, ref arg);
ITBasicCallControl bc = (ITBasicCallControl)ici;
bc.Hold(true);
}
catch (Exception exp)
{
System.Windows.Forms.MessageBox.Show("May not have any call to put on hold!\n\n" + exp.ToString(), "TAPI3");
}
IEnumCall ec1 = ia[line].EnumerateCalls();
uint arg1 = 0;
ITCallInfo ici1;
try
{
ec1.Next(1, out ici1, ref arg1);
ITBasicCallControl bc1 = ia[line].CreateCall("107",TapiConstants.LINEADDRESSTYPE_IPADDRESS, TapiConstants.TAPIMEDIATYPE_AUDIO);
bc1.Connect(false);
}
catch (Exception exp)
{
System.Windows.Forms.MessageBox.Show("Target looks unreachable!\n\n" + exp.ToString(), "TAPI3");
}
This part connects the current active line with the line on hold;
ITBasicCallControl callOnHold = null;
ITBasicCallControl callConnected = null;
IEnumCall enumCall = ia[line].EnumerateCalls();
ITCallInfo callInfo;
uint dummy = 0;
while (true)
{
enumCall.Next(1, out callInfo, ref dummy);
if (callInfo == null)
return;
if (callInfo.CallState == CALL_STATE.CS_HOLD)
callOnHold = (ITBasicCallControl)callInfo;
else if (callInfo.CallState == CALL_STATE.CS_CONNECTED)
callConnected = (ITBasicCallControl)callInfo;
if (callOnHold != null && callConnected != null)
break;
}
callOnHold.Transfer(callConnected, true);
callConnected.Finish(FINISH_MODE.FM_ASTRANSFER);
The initiation is as follows;
ITAddress[] ia = new TAPI3Lib.ITAddress[10];
...
try
{
tobj = new TAPIClass();
tobj.Initialize();
IEnumAddress ea = tobj.EnumerateAddresses();
ITAddress ln;
uint arg3 = 0;
lines = 0;
cn = new callnotification();
tobj.ITTAPIEventNotification_Event_Event += new TAPI3Lib.ITTAPIEventNotification_EventEventHandler(cn.Event);
tobj.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION |
TAPI_EVENT.TE_DIGITEVENT |
TAPI_EVENT.TE_PHONEEVENT |
TAPI_EVENT.TE_CALLSTATE |
TAPI_EVENT.TE_GENERATEEVENT |
TAPI_EVENT.TE_GATHERDIGITS |
TAPI_EVENT.TE_REQUEST);
for (int i = 0; i < 10; i++)
{
ea.Next(1, out ln, ref arg3);
ia[i] = ln;
if (ln != null)
{
lines++;
}
else
break;
}
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.ToString());
}
--Scenario
CallA: Incoming Call
CallB: Internal Call
Operator: The main answering operator
Bob: A user of this application
Alice: The caller of CallA
Bob uses his cellphone to call Alice's call center. The Operator sees a pop-up screen containing Bob's name and number and then picks up the phone. (CallA is stored). Bob requests to talk to Alice. The Operator clicks on "Alice" on the screen (CallA is now on Hold and the Operator's phone creates CallB which rings Alice's phone). Alice answers the line and agrees to take the call. The Operator clicks "Transfer" on screen and CallA and CallB are connected.
--End of Scenario
--Expected result
When the Operator calls Alice, the event sees that 100 called 107 (with 100 being the Operator's address on the PBX and 107 Alice's. When the Operator connects the 2 calls, I want to be able to pop-up the same screen that the Operator got that includes the information of the connected caller. What am i missing in order to fire that event? Because when i try to check the cln.Call.get_CallInfoString(TAPI3Lib.CALLINFO_STRING.CIS_CALLERIDNUMBER) all i see is 100 which is the Operator that called Alice and that is correct. What is the check i need to do in order to identify that a call has been transferred thus → get the caller ID → pop up the screen
--End of Expected result
I wish i was able to describe it as i have it mind. Thank you for your time and excuse me for the lengthy post.
The TAPI3 lib may have a bit different naming but the fields you are looking for are called ConnectedId, RedirectingId and RedirectionId on TAPI itself.
But it is very dependent on the PBX itself whether these fields get filled or not.
In a transfered call scenario "theoretically" you should see this in the call information:
caller id: source of the original call (= the customer)
called id: destination of the original call (= the published number of the company or service)
connected id: the actual current other party (= who you are speaking with)
redirecting id: the number that transferred the call to you (= operator extension)
redirection id: the number the redirecting party (= operator) transferred the call to, which "should" be the device you are observing this from (if no further forwarding was involved)
But none of this enforced by TAPI so a PBX can fill in what it wants, and different vendors will :(
Following Kris Vanherck's suggestion, I did a temporary measure to handle the transfers until we figure out a proper solution;
string con="000";
while (con.Length ==3 && con.Length >=3)
{
con = cln.Call.get_CallInfoString(TAPI3Lib.CALLINFO_STRING.CIS_CONNECTEDIDNUMBER);
}
Silly approach but hey it works. Can't wait to see a more appropriate way to do this. Again thanks Kris for the direction.
You're making it too hard on yourself. You don't need to call Hold manually, the Transfer Method does it for you.
When a new call from B comes in (TAPI_EVENT.TE_CALLNOTIFICATION), you can take its ITBasicCallControl to call Transfer on:
case TAPI_EVENT.TE_CALLNOTIFICATION:
ITBasicCallControl incomingCall = callNotification.Call;
break;
The Transfer Method takes a pointer to a new created call for consultation, so make sure you create a new call before hand:
//In this case address is of type ITAddress and represents the operators address.
ITBasicCallControl consultationCall = address.CreateCall("107", TapiConstants.LINEADDRESSTYPE_PHONENUMBER, TapiConstants.TAPIMEDIATYPE_AUDIO);
Now you can call Transfer:
incomingCall.Transfer(consultationCall, false);
Finally, when the consultation call disconnects, and Alice wants to take the call, use the Finish Method to finish transfering:
case CALL_STATE.CS_DISCONNECTED:
ITBasicCallControl consultationCall = (ITBasicCallControl )callStateEvent.Call;
consultationCall.Finish(FINISH_MODE.FM_ASTRANSFER);
break;
Related
I am trying to make a friends function into my Unity game. Each friend will have their own line with their name and a few buttons (challenge, about, etc.).
I have a friend row prefab and I instantiate it into the parent list for each friend.
It works just fine, until I click the challenge button, which whould call a method that takes in two parameters: the UId of the friend, and their username (two strings).
I am using Firebase Realtime Database for database.
void RetrieveFriendList(object sender, ValueChangedEventArgs args) {
foreach(Transform childTransform in listParent.GetComponentInChildren<Transform>()) {
GameObject.Destroy(childTransform.gameObject);
}
friends.Clear();
foreach (DataSnapshot s in args.Snapshot.Children) {
friends.Add(s.Key);
GameObject newRow = Instantiate(friendRowPrefab);
newRow.transform.Find("Deny").gameObject.GetComponent<Button>().onClick.RemoveAllListeners();
newRow.transform.Find("Challenge").gameObject.GetComponent<Button>().onClick.RemoveAllListeners();
newRow.transform.SetParent(listParent.transform);
newRow.transform.localScale = new Vector3(1f, 1f, 1f);
newRow.transform.Find("Text_Name").gameObject.GetComponent<TMPro.TMP_Text>().text = s.Child("username").Value.ToString();
string retrievedStatus = s.Child("type").Value.ToString();
if (retrievedStatus == "sent") {
newRow.transform.Find("Status").gameObject.GetComponent<TMPro.TMP_Text>().text = "Friend request sent";
} else if (retrievedStatus == "request") {
newRow.transform.Find("Status").gameObject.GetComponent<TMPro.TMP_Text>().text = "Incoming friend request";
newRow.transform.Find("Accept").gameObject.SetActive(true);
newRow.transform.Find("Deny").gameObject.SetActive(true);
newRow.transform.Find("Accept").gameObject.GetComponent<Button>().onClick.AddListener(delegate { AcceptFriendRequest(s.Key); });
newRow.transform.Find("Deny").gameObject.GetComponent<Button>().onClick.AddListener(delegate { DenyFriendRequest(s.Key); });
} else if (retrievedStatus == "friends") {
newRow.transform.Find("Challenge").gameObject.SetActive(true);
Debug.Log(s.Key + " - " + s.Child("username").Value.ToString());
newRow.transform.Find("Challenge").gameObject.GetComponent<Button>().onClick.AddListener(delegate { ChallengeFriend(s.Key, s.Child("username").Value.ToString()); }); //this is the line that causes the crash
newRow.transform.Find("About").gameObject.SetActive(true);
newRow.transform.Find("Status").gameObject.SetActive(false);
}
}
FirebaseDatabase.DefaultInstance.GetReference("users").Child(auth.CurrentUser.UserId).Child("friends").ValueChanged -= RetrieveFriendList;
}
What's likely happening is that the underlying C++ representation of your database snapshot is being cleaned up before your button accesses it. See this bug.
The easiest thing to do would be to find this line:
newRow.transform.Find("Challenge").gameObject.GetComponent<Button>().onClick.AddListener(delegate { ChallengeFriend(s.Key, s.Child("username").Value.ToString()); }); //this is the line that causes the crash
and turn it into something like:
var challengeKey = s.Key;
var challengeUsername = s.Child("username").Value.ToString();
newRow.transform.Find("Challenge").gameObject.GetComponent<Button>().onClick.AddListener(delegate { ChallengeFriend(challengeKey, challengeUsername); });
This way you retrieve the values you need (key and username) at the time of the callback rather than in the context of the button click (at some arbitrary point in the future after this function has returned). If you still get a crash, you may have to .Clone or .CopyTo the data -- but I believe that once an object is retrieved from the underlying snapshot it should be a full on C# object obeying the expected C# GC rules.
You also may experience null reference exceptions if the snapshot hits a local cache first -- so make sure you have null checks around everything (generally a good practice whenever you're hitting the web).
I have implemented Voice call in my code using .net with NServiceBus version 7.
Below is the code snippet to send voice call:
public Task Handle(AddServiceAuto message, IMessageHandlerContext context)
{
try
{
string VoiceCallCode = null;
Guid userID = User.userID;
VoiceCallCode = GetVoiceCallCode(userID);
if (VoiceCallCode != null)
{
publishAddVoiceCallEvent(context, user.caseID, userID.Mobile,
userID.Voicecall, VoiceMessageText, VoiceCallCode);
}
}
}
private void publishAddVoiceCallEvent(IMessageHandlerContext context,
Guid caseID, string mobile, bool voicecall,
string voiceMessageText, string voiceCallCode)
{
AddVoiceCallEvent addVoiceCallEvent = new AddVoiceCallEvent()
{
CaseID = caseID,
Mobile = mobile,
Voicecall = voicecall,
VoiceMessageText = voiceMessageText,
VoiceCallCode = voiceCallCode
};
context.Publish(addVoiceCallEvent).ConfigureAwait(false);
}
public Task Handle(AddVoiceCallEvent message, IMessageHandlerContext context)
{
try
{
Logger.InfoFormat("message.CaseID: {0}", message.CaseID);
Logger.InfoFormat("message.Voicecall= {0}", message.Voicecall);
Logger.InfoFormat("message.Mobile {0}", message.Mobile);
Logger.InfoFormat("message.VoiceCallCode {0}", message.VoiceCallCode);
// The user should satisfy below conditions in order to receive a voice call.
if ((message.Voicecall) && !string.IsNullOrEmpty(message.Mobile) &&
!string.IsNullOrEmpty(message.VoiceMessageText) &&
!string.IsNullOrEmpty(message.VoiceCallCode))
{
Voicecall(message.Mobile, message.Voicecall,
message.VoiceMessageText, message.VoiceCallCode);
}
else
{
Logger.Error("Mobile Value is Empty (OR) Voicecall is False (OR)
+ VoiceMessageText is Empty (OR) VoiceCallCode is Empty");
}
}
}
If condition satisfied it will send voice call, else it will print log.
Problem:
The Voice call is random i.e. sometimes user is receiving voice call and sometimes not(even though with same settings i.e mobile, VoiceCallCode values stored properly in DB and Voicecall is also true)
and the Strange part is, though the values are stored correctly DB, when we look into the logs that we are printing, it shows the value of Mobile, VoiceCallCode is null and Voicecall is false.
Again after 5 mins I tried, it worked.
One more thing is, when voice call is not working.
Logger.InfoFormat("message.CaseID: {0}", message.CaseID); // CaseID printed
For Below, data is not printing even though data is there in available in DB (i.e. printing as null)
Logger.InfoFormat("message.Voicecall= {0}", message.Voicecall);
Logger.InfoFormat("message.Mobile {0}", message.Mobile);
Logger.InfoFormat("message.VoiceCallCode {0}", message.VoiceCallCode);
Strange is that, for CaseID it printed while for others it is not printing.
Why this is happening? Can someone please help on this?
The code you've shared doesn't seem to be a running code (try w/o catch) therefore it would be hard to pinpoint what contributes to the issue. But the random behaviour could be attributed to improper use of async APIs. The handler methods should return a Task or use async/await. So are operations invoked on IMessageHandlerContext.
For example, publishAddVoiceCallEvent should be returning a Task and not void. The code inside it (context.Publish(addVoiceCallEvent).ConfigureAwait(false);) should be either return context.Publish(addVoiceCallEvent); or await context.Publish(addVoiceCallEvent).ConfigureAwait(false);.
NServiceBus comes with a Rozlyn analyzer to help with these issues.
I need to create a program to monitor activity of phone call.And get information about the calls, like number and Name.
I'm not strong with TAPI code and C#, so hope that anybody can help me, I'm desperate.
I have this code where I try to detect the available devices and obtain information from those devices when a call comes in:
using System;
using TAPI3Lib;
using JulMar.Atapi;
namespace ConsoleApp1
{
class Program
{
private void tapi_ITTAPIEventNotification_Event(TAPI_EVENT TapiEvent, object pEvent)
{
try
{
ITCallNotificationEvent cn = pEvent as ITCallNotificationEvent;
if(cn.Call.CallState == CALL_STATE.CS_CONNECTED)
{
string calledidname = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNAME);
Console.WriteLine("Called ID Name " + calledidname);
string callednumber = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNUMBER);
Console.WriteLine("Called Number " + callednumber);
string calleridname = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNAME);
Console.WriteLine("Caller ID Name " + calleridname);
string callernumber = cn.Call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNUMBER);
Console.WriteLine("Caller Number " + callernumber);
}
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
static void Main(string[] args)
{
TapiManager mgr = new TapiManager("ConsoleApp1");
mgr.Initialize();
foreach(TapiLine line in mgr.Lines)
{
foreach (string s in line.Capabilities.AvailableDeviceClasses)
Console.WriteLine("{0} - {1}", line.Name, s);
}
}
}
}
But when I run it, just see the available devices but don't see any information about calls. I'm used to programming in java so I guess I should send to call the method that gets the call information in the main, but I do not know how to do that and in any code I've seen they do.
So, hope that you can help me to understand how TAPI works and what can I do to do my code work.
Okay, first, you want to stick to one Version of TAPI. In your using statements, youre importing a TAPI 2.x managed libaray and a TAPI 3.x managed library.
using TAPI3Lib; // this is a TAPI 3.x library
using JulMar.Atapi; // this is a TAPI 2.x library
If you're choosing to go with TAPI 3.x, you should start by creating a new class, which handles different kinds of TAPI events. To do so, it needs to implement the ITTAPIEventNotification interface:
public class CallNotification : ITTAPIEventNotification
{
public void Event(TAPI_EVENT TapiEvent, object pEvent)
{
if(pEvent == null)
throw new ArgumentNullException(nameof(pEvent));
switch (TapiEvent)
{
case TAPI_EVENT.TE_CALLNOTIFICATION:
// This event will be raised every time a new call is created on an monitored line-
// You can use CALLINFO_LONG.CIL_ORIGIN to see weather it's an inbound call, or an
// outbound call.
break;
case TAPI_EVENT.TE_CALLSTATE:
// This event will be raised every time the state of a call on one of your monitored
// Lines changes.
// If you'd want to read information about a call, you can do it here:
ITCallStateEvent callStateEvent = (ITCallStateEvent)pEvent;
ITCallInfo call = callStateEvent.Call;
string calledidname = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNAME);
Console.WriteLine("Called ID Name " + calledidname);
string callednumber = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLEDIDNUMBER);
Console.WriteLine("Called Number " + callednumber);
string calleridname = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNAME);
Console.WriteLine("Caller ID Name " + calleridname);
string callernumber = call.get_CallInfoString(CALLINFO_STRING.CIS_CALLERIDNUMBER);
Console.WriteLine("Caller Number " + callernumber);
break;
}
// Since you're working with COM objects, you should release any used references.
Marshal.ReleaseComObject(pEvent);
}
}
In order to use this class, you need to create a new instance of TAPI3Lib.TAPIClass and call its Initialize method. After that, you can attach your newly create CallNotification class as an event handler. You could also specify which types of events you want your handler to receive. Notice that you wont receive any event notifications at this point, because you haven't told TAPIClass which lines it should monitor:
CallNotification callevent = new CallNotification();
TAPIClass tapi = new TAPIClass();
tapi.Initialize();
tapi.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION | TAPI_EVENT.TE_CALLSTATE);
tapi.ITTAPIEventNotification_Event_Event += new ITTAPIEventNotification_EventEventHandler(callevent.Event);
In order to tell TAPIClass which lines it should monitor, you need to do two things. ask for all lines registered to you IPBX and determine, weather its a line you have the rights to monitor (this is an IPBX configuration):
public List<ITAddress> EnumerateLines(TAPIClass tapi)
{
List<ITAddress> addresses = new List<ITAddress>();
ITAddress address;
uint arg = 0;
ITAddressCapabilities addressCapabilities;
int callfeatures;
int linefeatures;
bool hasCallFeaturesDial;
bool hasLineFeaturesMakeCall;
IEnumAddress ea = tapi.EnumerateAddresses();
do
{
ea.Next(1, out address, ref arg);
if (address != null)
{
addressCapabilities = (ITAddressCapabilities)address;
callfeatures = addressCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_CALLFEATURES1);
linefeatures = addressCapabilities.get_AddressCapability(ADDRESS_CAPABILITY.AC_LINEFEATURES);
hasCallFeaturesDial = (callfeatures1 & (int)0x00000040) != 0; //Contains LineCallFeatures Dial; see Tapi.h for details
hasLineFeaturesMakeCall = (linefeatures & (int)0x00000008) != 0; //Contains LineFeatures MakeCall; see Tapi.h for details
// this is basically saying "Am I allowed to dial numbers and create calls on this specific line?"
if(hasCallFeaturesDial && hasLineFeaturesMakeCall)
addresses.Add(address);
}
} while (address != null);
return addresses;
}
public void RegisterLines(TAPIClass tapi, IEnumerable<ITAddress> addresses)
{
if (tapi == null)
throw new ArgumentNullException(nameof(tapi));
if (addresses == null)
throw new ArgumentNullException(nameof(addresses));
foreach (ITAddress address in addresses)
{
tapi.RegisterCallNotifications(address, true, true, TapiConstants.TAPIMEDIATYPE_AUDIO, 2);
}
}
so Your initialization would look like this:
CallNotification callevent = new CallNotification();
TAPIClass tapi = new TAPIClass();
tapi.Initialize();
IEnumerable<ITAddress> addresses = this.EnumerateLines(tapi);
this.RegisterLines(tapi, addresses);
tapi.EventFilter = (int)(TAPI_EVENT.TE_CALLNOTIFICATION | TAPI_EVENT.TE_CALLSTATE);
tapi.ITTAPIEventNotification_Event_Event += callevent.Event;
Once you run your program, and it's done executing above code, you will get notifications from incoming and outgoing calls when their call state changes.
I hope you could follow this post. If you have any questions, just ask =)
The line is TapiLine, you have to use TapiCall.
I am using fiddlercore to capture session information to run a compare on the data in a particular response. One of the things I am noticing that I don't understand is that I am getting session information from the second environment into the List collection I have for the first.
public class ManageCompares
{
public static string _test2BaseURL = "https://test2/";
public static string _dev1BaseURL = "http://dev1/";
private void RunCompares(string email, string handler, Reporting report)
{
ManageProcess.ShutDownProcess("iexplore");
RunExports exportTest2 = new RunExports();
RunExports exportDev1 = new RunExports();
string password = "d";
List<Session> oAllSessions_Test2 = exportTest2.RunExportGeneration
(email, password, _test2BaseURL, handler);
ManageProcess.ShutDownProcess("iexplore");
List<Session> oAllSessions_Dev1 = exportDev1.RunExportGeneration
(email, password, _dev1BaseURL, handler);
exportTest2.ExtractResponse(oAllSessions_Test2, handler, report);
//report.SetEnvironment2Body(ManageExports.ExtractResponse
// (oAllSessions_Dev1, handler, report, report.Environment2));
if (report.Test2ResponseCode != 500 && report.Dev1ResponseCode != 500)
{
bool matches = CompareExports.CompareExportResults
(report.Environment1Body, report.Environment2Body);
if (matches)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Exports matched");
Console.ResetColor();
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Export does not match");
Console.ResetColor();
report.GenerateReportFiles();
}
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine
("A exception was returned. Please review the log file.");
Console.ResetColor();
}
}
}
public class RunExports
{
public List<Session> RunExportGeneration
(string email, string password, string baseUrl,
string handlersUrlwithParams)
{
IWebDriver driver = new InternetExplorerDriver();
FiddlerApplication.Startup(8877, FiddlerCoreStartupFlags.Default);
List<Session> oAllSessions = new List<Session>();
LoginPage login = new LoginPage(driver);
FiddlerApplication.AfterSessionComplete += delegate(Session oS)
{
Monitor.Enter(oAllSessions);
oAllSessions.Add(oS);
Monitor.Exit(oAllSessions);
};
try
{
driver.Navigate().GoToUrl(baseUrl);
login.LoginToView(email, password);
driver.Navigate().GoToUrl(baseUrl + handlersUrlwithParams);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
FiddlerApplication.Shutdown();
driver.Quit();
}
return oAllSessions;
}
}
List oAllSessions_Test2 and List oAllSessions_Dev1 are my two collections. When I debug the capture I typically see 15 rows in the oAllSessions_Test2 collection. Then after capturing oAllSessions_Dev1 I see the count has jumped up 14 or 15 and when I look at what is contained by the colleciton some of the Dev1 captures are now in there. oAllSessions_Dev1 has just the sessions I am expecting. I am guessing there must be a pointer someplace I am not expecting but I am stumped at this point how to clear it up. The other thing that I am noticing is that the session counter continues to increment while the application is cycling through the various cases.
I am also using Selenium WebDriver and IE to initiate the browser session but I don't think that is particularly relevant to this particular issue.
So what am I missing here?
Do this :
SessionStateHandler tAction = oS =>
{
Monitor.Enter(oAllSessions);
oAllSessions.Add(oS);
Monitor.Exit(oAllSessions);
};
FiddlerApplication.AfterSessionComplete += tAction;
......
//at the end before your return statement:
FiddlerApplication.AfterSessionComplete -= tAction;
So here is what's going on.
this: FiddlerApplication.Startup(8877, FiddlerCoreStartupFlags.Default);
(this specifically) FiddlerApplication
is holding onto references in an external application (fiddler) and administrating them for you. When you += and add a delegate FiddlerApplication.AfterSessionComplete += tAction;, the fiddler application was adding this to the list of methods it calls when the AfterSession event fires.
Because it's singleton (You are only dealing with one Fiddler application instance in your code), every time you do a += it adds it to the same list. This list in the FiddlerApplication doesn't get recreated every time you call your method. It's the same one that you called the first time, so even though your delegate falls out of it's scope declaration space (like local objects normally do in a method), the FiddlerApplication EventList maintains a reference to it and fires that delegate each time (and every other one).
So.....
In your method you create List<Session> oAllSessions = new List<Session>(); and access it in your delegate. This local variable is now passed back to the calling method, List<Session> oAllSessions_Test2 = exportTest2.RunExportGeneration.... and the delegate FiddlerApplication calls is the exact same list. So each time you call that method and the AfterSessionComplete fires, it updates the list even after it's been returned to the calling function.
To stop it you have to add: FiddlerApplication.AfterSessionComplete -= tAction; which tells the system "Hey, don't push updates to this method anymore. It's done receiving notifications."
In .NET languages class objects are passed by reference in almost all cases. If you assign an object reference to a variable, that variable will still refer to the original object. Changes to the original will be visible through all references. This is similar to how things used to happen in C/C++ when you stored pointers or references to structures in memory. The implementation details are different, but the results are the same.
Here's an example of what happens when you store list instances that might be changed later:
class ListTest
{
List<string> l = new List<string>();
public List<string> GetList() { return l; }
public void Add(string v) { l.Add(v); }
}
class Program
{
static void Main(string[] args)
{
ListTest t = new ListTest();
t.Add("a"); t.Add("b"); t.Add("c"); t.Add("d");
List<string> x1 = t.GetList();
List<string> x2 = t.GetList().ToList();
t.Add("e"); t.Add("f"); t.Add("g"); t.Add("h");
List<string> y1 = t.GetList();
List<string> y2 = t.GetList().ToList();
Console.WriteLine("{0}, {1}", x1.Count, y1.Count);
Console.WriteLine("{0}", string.Join(", ", x1));
Console.WriteLine("{0}", string.Join(", ", y1));
Console.WriteLine();
Console.WriteLine("{0}, {1}", x2.Count, y2.Count);
Console.WriteLine("{0}", string.Join(", ", x2));
Console.WriteLine("{0}", string.Join(", ", y2));
}
}
When you run that you get one set of identical results, because x1 and y1 are references to the same object. The second set of results are different because the call to ToList creates a new List<string> instance to hold the results.
This might be what is happening with the list that is returned by your Fiddler code.
--
Update after code review
What it looks like is that the delegate you're assigning to the AfterSessionComplete event is causing the code to treat oAllSessions as a static object bound to the event handler delegate. Various side-effects like this happen when you start playing around with code that generates closures and so on.
I would suggest changing the code to use a class method rather than an inline method - shift your oAllSessions variable and the code you're assigning to AfterSessionComplete out into the body of the class. That will at least establish whether the inline delegate is the cause of the problem.
So what I am trying to do is set a screen resolution on the primary display in C# using ChangeDisplaySettings. I have tested this across multiple windows 7 computers that I own, and the result is always the same: any* valid resolution (those that are listed in the "screen resolution" menu upon right-clicking the desktop) will work just fine, except for the largest resolution that can be selected in that menu, which will cause User32.ChangeDisplaySettings to return -2, which is DISP_CHANGE_BADMODE, meaning that the requested display mode was invalid.
*On some of the computers with larger primary displays, I didn't bother to test every resolution, and just picked some arbitrary smaller ones & the maximum, as there were too many to test on every single one. I feel confident enough in my testing to say that the maximum resolution ALWAYS fails, while smaller resolutions usually/always succeed (I never had any of them fail during my tests anyways).
Documentation on ChangeDisplaySettings: http://msdn.microsoft.com/en-us/library/dd183411%28VS.85%29.aspx
Documentation on the DEVMODE struct it uses: http://msdn.microsoft.com/en-us/library/dd183565%28v=vs.85%29.aspx
So for an example, lets say I'm running on a 1920x1080 display.
I set my resolution manually (or programmatically) to something else, doesn't matter what it is or how it was changed, and then run the following code:
DEVMODE dm = new DEVMODE();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
if (User32.EnumDisplaySettings(null, User32.ENUM_CURRENT_SETTINGS, ref dm) != 0)
{
dm.dmPelsWidth = 1920;
dm.dmPelsHeight = 1080;
Console.WriteLine("" + User32.ChangeDisplaySettings(ref dm, User32.CDS_UPDATEREGISTRY) + "\n");
}
*Note that this is not actually the code from the program. I just made this simplified version to cut it down to the bare necessities to illustrate the point.
The program would print out:
-2
Which, as mentioned previously, is the value of DISP_CHANGE_BADMODE, and the resolution would fail to change.
Now, if I changed the values of 1080 and 1920 to 900 and 1600 respectively, another supported resolution on this monitor, and then set the resolution to something other than 1600x900, and then ran this program, it would in fact change the resolution to 1600x900, and return DISP_CHANGE_SUCCESSFUL.
Note that using other flags, such as CDS_RESET (0x40000000 or 1073741824) in place of CDS_UPDATEREGISTRY also result in the same error.
This is a tutorial I found that helped me get started:
www.codeproject.com/Articles/6810/Dynamic-Screen-Resolution
[I removed the hyperlink because of the apparent spam prevention system. Sort of silly given that the first to were msdn.microsoft links, and this is a codeproject one, but, w/e]
Note that in the comments section, it seems there is someone who used the provided source files directly, and is experiencing a similar problem. To quote them:
hello ,
i'm using Resolution.cs on my c# application
and it doesn't work with high resolutions like " 1366*768 " & " 1280*720 "
can any one help ???
However, despite how commonly ChangeDisplaySettings seems to be recommended by tutorials, I can't find any information on resolving this issue (which could very well be operating system specific, but I currently lack any non-Windows 7 computers to test on, and even if I did, it would not solve the issue that it doesn't work on Windows 7 computers)
As it turns out, the tutorial I was using was making the assumption that nothing would change any of the display mode parameters to something invalid during the time that it was at a lower resolution (Such as increasing the refresh rate, which is capped on the monitor's side. And since I was using the same two monitors for the testing, and the max resolution had a lower max refresh rate than any of the other resolutions, I would encounter this issue.)
A safer method than what is illustrated in the tutorial is to work with the indexes of the display modes or work only with EnumDisplayModes and never touch the data inside the DEVMODE struct.
The following is an excerpt of an example program I whipped up that changes the resolution to the specified parameters, and then back again.
int selB, selG, selF, selH, selW;
... //these values get defined, etc.
DEVMODE OSpecs = new DEVMODE();
getCurrentRes(ref OSpecs);
int Ondx = getDMbySpecs(OSpecs.dmPelsHeight, OSpecs.dmPelsWidth, OSpecs.dmDisplayFrequency, OSpecs.dmDisplayFlags, OSpecs.dmBitsPerPel, ref OSpecs);
Screen Srn = Screen.PrimaryScreen;
Console.WriteLine("Current res is " + OSpecs.dmPelsHeight + " by " + OSpecs.dmPelsWidth + "\n");
DEVMODE NSpecs = new DEVMODE();
int Nndx = getDMbySpecs(selH, selW, selF, selG, selB, ref NSpecs);
//Note that this function sets both the DEVMODE to the selected display mode and returns the index value of this display mode. It returns -1 if it fails (-1 is the value of ENUM_CURRENT_SETTINGS), and sets the DEVMODE to the current display mode.
if (Nndx == -1)
{
Console.WriteLine("Could not find specified mode");
}
else if (setDisplayMode(ref NSpecs) || setDisplayMode(Nndx)) //This is just to illustrate both ways of doing it. One or the other may be more convenient (ie, the latter if you are getting this from a file, the former if you already have the DEVMODE in your program, etc.)
{
//reset display mode to original after waiting long enough to see it changed
Console.WriteLine("Successful change. Waiting 4 seconds.");
Thread.Sleep(4000);
if (setDisplayMode(ref OSpecs) || setDisplayMode(Ondx))
{
//success!
Console.WriteLine("Mode reversion succeeded.");
}
else
{
Console.WriteLine("Mode reversion failed. Manual reset required.");
}
}
else
{
//return
Console.WriteLine("Resolution change failed. Aborting");
}
where the functions used here are as follows:
static bool setDisplayMode(int i)
{
DEVMODE DM = new DEVMODE();
DM.dmSize = (short)Marshal.SizeOf(DM);
User32.EnumDisplaySettings(null, i, ref DM);
if (User32.ChangeDisplaySettings(ref DM, User32.CDS_TEST) == 0 && User32.ChangeDisplaySettings(ref DM, User32.CDS_UPDATEREGISTRY) == 0)
{
return true;
}
else
{
return false;
}
}
static bool setDisplayMode(ref DEVMODE DM)
{
if (User32.ChangeDisplaySettings(ref DM, User32.CDS_TEST) == 0 && User32.ChangeDisplaySettings(ref DM, User32.CDS_UPDATEREGISTRY) == 0)
{
return true;
}
else
{
return false;
}
}
static int getDMbySpecs(int H, int W, int F, int G, int B, ref DEVMODE DM)
{
DM.dmSize = (short)Marshal.SizeOf(DM);
DEVMODE SelDM = new DEVMODE();
SelDM.dmSize = (short)Marshal.SizeOf(SelDM);
int iOMI = 0;
for (iOMI = 0; User32.EnumDisplaySettings(null, iOMI, ref SelDM) != 0; iOMI++)
{
if (( B == -1 || B == SelDM.dmBitsPerPel) && ( H == -1 || H == SelDM.dmPelsHeight) && ( W == -1 || W == SelDM.dmPelsWidth) && ( G == -1 || G == SelDM.dmDisplayFlags) && ( F == -1 || F == SelDM.dmDisplayFrequency))
break;
}
if (User32.EnumDisplaySettings(null, iOMI, ref DM) == 0)
{
iOMI = -1;
getCurrentRes(ref DM);
}
return iOMI;
}
static void getCurrentRes(ref DEVMODE dm)
{
dm = new DEVMODE();
dm.dmSize = (short)Marshal.SizeOf(dm);
User32.EnumDisplaySettings(null, User32.ENUM_CURRENT_SETTINGS, ref dm);
return;
}
Apart from the answer that #Avan has given, I found that this problem also happens when you pass wrong parameter to the dmDisplayFrequency of DevMode structure
To solve this, you can first get all the available screen resolutions from Graphics device and then update the frequency before sending it to the ChangeDisplaySettings function.
To get display settings list use EnumDisplaySettings
For switching to the highest native resolution you must fill in the dm.dmFields structure with valid values. It seems. Before calling ChangeDisplaysettings().