I'm trying to find all the builds and releases that are configured to use a specific agent pool, using the .NET Client Libraries.
Assuming agentPoolId, I can get all the build definitions like this:
// _connection is of type VssConnection
using (var buildClient = _connection.GetClient<BuildHttpClient>())
{
List<BuildDefinitionReference> allBuilds = await buildClient.GetDefinitionsAsync(projectName, top: 1000, queryOrder: DefinitionQueryOrder.DefinitionNameAscending);
List<BuildDefinitionReference> builds = allBuilds.Where(x => HasAgentPoolId(x, agentPoolId)).ToList();
}
private bool HasAgentPoolId(BuildDefinitionReference buildDefinition, int agentPoolId)
{
TaskAgentPoolReference pool = buildDefinition?.Queue?.Pool;
if (pool == null)
{
return false;
}
return pool.Id.Equals(agentPoolId);
}
But I couldn't find a way to find the release definitions that have one or more environments configured to use a particular agent. Any suggestion?
I was manged to get all releases by Agent Pool ID via Rest Api and not via NET Client Libraries.Hope that helps.
C# Code snippet:
public class ReleaseResponse
{
[JsonProperty("value")]
public List<ReleaseItem> Value { get; set; }
}
public class ReleaseItem
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("Id")]
public int Id { get; set; }
}
static void Main(string[] args)
{
string tfsURL = "TFS URL";
string releaseDefurl = $"{tfsURL}/_apis/release/definitions?$expand=artifacts&api-version=3.2-preview.3";
const int agentPoolID = "AGENT Pool ID";
List<string> relevantReleases = new List<string>();
WebClient client = new WebClient();
client.UseDefaultCredentials = true;
client.Headers.Add("Content-Type", "application/json");
var releaseList = client.DownloadString(releaseDefurl);
var allReleases = JsonConvert.DeserializeObject<ReleaseResponse>(releaseList).Value;
foreach (var release in allReleases)
{
string releaseInfoApi = $"{tfsURL}/_apis/Release/definitions/{release.Id}";
var getReleseInfo = client.DownloadString(releaseInfoApi);
var releaseInfo = JsonConvert.DeserializeObject<TFSLogic.RootObject>(getReleseInfo);
var deploymentAgents = releaseInfo.environments.ToList().Where(e => e.deployPhases.FirstOrDefault().deploymentInput.queueId == agentPoolID).Count();
if (deploymentAgents > 0)
{
relevantReleases.Add(release.Name);
}
}
}
Find TFSLogic here : https://codebeautify.org/online-json-editor/cb7aa0d9
Powershell Code snippet:
$tfsUrl = "TFS URL"
$releaseDefurl = $tfsUrl + '/_apis/release/definitions?$expand=artifacts&api-version=3.2-preview.3'
$agentPoolID = "Agent Pool ID"
$relevantReleases = #();
$allReleasesID = (Invoke-RestMethod -Uri ($releaseDefurl) -Method Get -UseDefaultCredentials).value.id
function getReleaseByAgentPoolID($releaseID,$agentPoolID)
{
$ReleaseInfo = Invoke-RestMethod -Uri "$tfsUrl/_apis/Release/definitions/$releaseID" -Method Get -UseDefaultCredentials
$deploymentAgents = $ReleaseInfo.environments | % {$_.deployPhases.deploymentInput.queueId} | where {$_ -eq $agentPoolID}
if($deploymentAgents.Count -gt 0)
{
return $ReleaseInfo.name
}
}
foreach ($releaseID in $allReleasesID)
{
$relevantReleases += getReleaseByAgentPoolID -releaseID $releaseID -agentPoolID $agentPoolID
}
UPDATE :
It took me some time,But i was able to achieve that with azure-devops-dotnet-samples
I hope this example is finally what you are looking for.
using Microsoft.VisualStudio.Services.WebApi;
using System;
using System.Linq;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Clients;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Contracts;
using Microsoft.VisualStudio.Services.Common;
using System.Collections.Generic;
namespace FindReleaseByAgentPoolID
{
class Program
{
const int agentPoolID = 999;
static void Main(string[] args)
{
var relevantReleases = new List<string>();
VssCredentials c = new VssCredentials(new WindowsCredential(System.Net.CredentialCache.DefaultNetworkCredentials));
var tfsURL = new Uri("TFS URL");
var teamProjectName = "PROJECT";
using (var connection = new VssConnection(tfsURL, c))
using (var rmClient = connection.GetClient<ReleaseHttpClient2>())
{
var releases = rmClient
.GetReleaseDefinitionsAsync(teamProjectName, string.Empty, ReleaseDefinitionExpands.Environments)
.Result.ToArray();
foreach (var release in releases)
{
var r = rmClient.GetReleaseDefinitionAsync(teamProjectName, release.Id);
var deploymentAgents = r.Result.Environments.SelectMany(e =>
e.DeployPhases.Select(dp =>
dp.GetDeploymentInput()).Cast<DeploymentInput>()).Where(di =>
di.QueueId == agentPoolID).Count();
if (deploymentAgents > 0)
{
relevantReleases.Add(release.Name);
}
}
}
}
}
}
Found a solution, many thanks to #amit-baranes for pointing me in the right direction.
I've changed his code sample to use the await keyword instead of using .Result, and use .OfType<DeploymentInput>() instead of .Cast<DeploymentInput>() (it was throwing some exceptions).
Oh, and the most important thing I've learned: agent pool ID and queue ID are different things!!! If you intend to use the agent pool ID to get the release definitions you'll need to get the correspondent agent queue.
Code sample:
// set agent pool Id and project name
int agentPoolId = 123456;
string teamProjectName = ".....";
// _connection is of type VssConnection
using (var taskAgentClient = _connection.GetClient<TaskAgentHttpClient>())
using (var releaseClient = _connection.GetClient<ReleaseHttpClient2>())
{
// please note: agent pool Id != queue Id
// agent pool id is used to get the build definitions
// queue Id is used to get the release definitions
TaskAgentPool agentPool = await taskAgentClient.GetAgentPoolAsync(agentPoolId);
List<TaskAgentQueue> queues = await taskAgentClient.GetAgentQueuesByNamesAsync(teamProjectName, queueNames: new[] { agentPool.Name });
TaskAgentQueue queue = queues.FirstOrDefault();
List<ReleaseDefinition> definitions = await releaseClient.GetReleaseDefinitionsAsync(teamProjectName, string.Empty, ReleaseDefinitionExpands.Environments);
foreach (ReleaseDefinition definition in definitions)
{
var fullDefinition = await releaseClient.GetReleaseDefinitionAsync(teamProjectName, definition.Id);
bool hasReleasesWithPool = fullDefinition.Environments.SelectMany(GetDeploymentInputs)
.Any(di => di.QueueId == queue.Id);
if (hasReleasesWithPool)
{
Debug.WriteLine($"{definition.Name}");
}
}
}
private IEnumerable<DeploymentInput> GetDeploymentInputs(ReleaseDefinitionEnvironment environment)
{
return environment.DeployPhases.Select(dp => dp.GetDeploymentInput())
.OfType<DeploymentInput>();
}
Related
I need to get (not download) the content from 10.000~ manifest files within a project in Azure DevOps, but I don't manage to achieve this. I have found several ways to retrieve the content from one file at a time, but in this context, it is neither an efficient nor sustainable solution. I have managed to retrieve all files of a particular file type by checking if the file path ends with the name of the file, then using the TfvcHttpClientBase.GetItemsBatch method. However, this method does not return the item's content.
Program.cs
using Microsoft.TeamFoundation.SourceControl.WebApi;
AzureRest azureRest = new AzureRest();
var tfvcItems = azureRest.GetTfvcItems();
List<TfvcItemDescriptor> itemDescriptorsList = new List<TfvcItemDescriptor>();
foreach(var item in tfvcItems)
{
//Example manifest file .NET
if (item.Path.EndsWith("packages.config"))
{
var itemDescriptor = new TfvcItemDescriptor()
{
Path = item.Path,
RecursionLevel = VersionControlRecursionType.None,
Version = "",
VersionOption = TfvcVersionOption.None,
VersionType = TfvcVersionType.Latest
};
itemDescriptorsList.Add(itemDescriptor);
}
}
TfvcItemDescriptor[] itemDescriptorsArray = itemDescriptorsList.ToArray();
var itemBatch = azureRest.GetTfvcItemsBatch(itemDescriptorsArray);
foreach(var itemList in itemBatch)
{
foreach(var itemListList in itemList)
{
Console.WriteLine("Content: " + itemListList.Content); //empty/null
Console.WriteLine("ContentMetadata: " + itemListList.ContentMetadata); //not empty/null
}
}
AzureRest.cs
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
public class AzureRest
{
const string ORG_URL = "https://org/url/url";
const string PROJECT = "Project";
const string PAT = "PersonalAccessToken";
private string GetTokenConfig()
{
return PAT;
}
private string GetProjectNameConfig()
{
return PROJECT;
}
private VssConnection Authenticate()
{
string token = GetTokenConfig();
string projectName = GetProjectNameConfig();
var credentials = new VssBasicCredential(string.Empty, token);
var connection = new VssConnection(new Uri(ORG_URL), credentials);
return connection;
}
public List<TfvcItem> GetTfvcItems()
{
var connection = Authenticate();
using (TfvcHttpClient tfvcClient = connection.GetClient<TfvcHttpClient>())
{
var tfvcItems = tfvcClient.GetItemsAsync(scopePath: "/Path", recursionLevel: VersionControlRecursionType.Full, true).Result;
return tfvcItems;
}
}
public List<List<TfvcItem>> GetTfvcItemsBatch(TfvcItemDescriptor[] itemDescriptors)
{
TfvcItemRequestData requestData = new TfvcItemRequestData()
{
IncludeContentMetadata = true,
IncludeLinks = true,
ItemDescriptors = itemDescriptors
};
var connection = Authenticate();
using (TfvcHttpClient tfvcClient = connection.GetClient<TfvcHttpClient>())
{
var tfvcItems = tfvcClient.GetItemsBatchAsync(requestData).Result;
return tfvcItems;
}
}
}
}
For reference:
I have tested the codes you shared and when debugging at "itemDescriptorsList" and have found that there is no content specified in it, so that's why you cannot get the txt content.
You should first check and add the content property into the "itemDescriptorsList".
I make SOAP requests to an e-commerce API to know how many orders were made for a specified product, in a given time frame.
My method takes a product ID and a number of days to create a time frame. Then it constructs a SOAP request and returns the amount sold products.
public static async Task<int?> GetOrdersFromApi(int providedId, int days) {
var dateFrom = DateTime.Now.AddDays(days * -1).ToString("yyyy-MM-dd") + " 00:00:00";
var dateTo = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
float orderedCount = 0;
int orderedCountToPass = 0;
int i = 0;
var binding = new BasicHttpBinding {
MaxReceivedMessageSize = 40000000
};
var address = new EndpointAddress("http://example.com/api/get/");
using (var client = new ApiOrdersPortTypeClient(binding, address)) {
try {
while (true) {
// request parameters
var request = new ApiOrdersGetAsync.requestType {
authenticate = new ApiOrdersGetAsync.authenticateType {
userLogin = "Username",
authenticateKey = "Key",
}
};
request.#params = new ApiOrdersGetAsync.paramsType {
ordersStatuses = new string[] { "finished" },
products = new productType[1],
};
request.#params.products[0] = new productType {
productIdSpecified = true,
productId = providedId,
};
request.#params.ordersRange = new ordersRangeType {
ordersDateRange = new ordersDateRangeType()
};
request.#params.ordersRange.ordersDateRange.ordersDateTypeSpecified = true;
request.#params.ordersRange.ordersDateRange.ordersDateType = ordersDateTypeType.dispatch;
request.#params.ordersRange.ordersDateRange.ordersDateBegin = dateFrom;
request.#params.ordersRange.ordersDateRange.ordersDateEnd = dateTo;
request.#params.resultsPageSpecified = true;
request.#params.resultsPage = i;
// processing the result
var results = await client.getAsync(request);
foreach (var result in results.Results) {
int productsResultsPage = 0;
foreach (var product in result.orderDetails.productsResults) {
try {
if (result.orderDetails.productsResults[productsResultsPage ].productId == providedId) {
orderedCount += result.orderDetails.productsResults[y].productQuantity;
productsResultsPage++;
}
} catch (IndexOutOfRangeException ex) {
// error is thrown to escape loop - sloppy, I know
Console.WriteLine(ex);
};
}
};
orderedCountToPass = (int)orderedCount;
orderedCount = 0;
i++;
};
} catch (NullReferenceException) {
// do nothing, we just want to exit while loop when i is higher than page count
}
return orderedCountToPass;
};
}
The result often should be in hundreds, but regardless how well a product sells, it returns something from 0 to 4.
Here is a sample response:
For example, I'm only interested with productId == 479, but an order was made with other products as well that don't interest me - I need to filter them.
I'm doing something wrong with how I try to filter results. How do I do it properly? I'm certain the request is correct and response does contain all possible orders.
You are getting xml results so use Net Xml library :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string URL = #"Enter URL Here";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(URL);
List<XElement> items = doc.Descendants("item").ToList();
}
}
}
Below is a script making an API call which retrieves some ReportIDs (from a database table), passes them through a method which will run them individually, and finally see if each contains the field, Name_First.
What I need help with is creating a count that will tell me how many reports contain the field Name_First, how many don't, and what's the percentage out of all reports having the field, Name_First.
Currently, the script queries two ReportIDs (12300,12301) for testing. One report has the field Name_First in it and the other one doesn't. From the foreach loop I've written, I'm aiming to count every time there is a null and every time there isn't, get a total, and just divide the not null count by the sum of both. However, when I run this script, the console at
Console.WriteLine(total);
Console.WriteLine(total_noFirstName);
Console.WriteLine(total_FirstName);
returns values of 0, 0, and 0. I think I'm having a scope issue here, but I'm not really sure. If I could receive some help solving this, I'd very much appreciate it.
Thank you!
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
namespace NameFirstSearch
{
class Program
{
static void Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
const string username = "Username";
const string password = "Password";
const string baseUrl = "https://test.com/rest/services/";
const string queryString = "query?q=Select * From Report Where ReportID in (12300,12301)";
const string queryNameFirst = "getreport/";
var client = new HttpClient();
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var auth = Convert.ToBase64String(Encoding.Default.GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
//GetReports(client, queryString).Wait();
var reportsList = GetReports(client, queryString).Result;
GetNameFirst(client, queryNameFirst, reportsList).Wait();
Console.ReadLine();
}
static async Task<List<Properties>> GetReports(HttpClient client, string queryString)
{
List<Properties> result = new List<Properties>();
var response = await client.GetAsync(queryString);
// Check for a successfull result
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<List<Properties>>(json);
}
else
{
// Error code returned
Console.WriteLine("No records found on first method.");
}
return result;
}
static async Task GetNameFirst(HttpClient client, string queryNameFirst, List<Properties> results)
{
string reportType = ".json";
foreach (var item in results)
{
var output = await client.GetAsync(queryNameFirst + item.ReportID + reportType);
if (output.IsSuccessStatusCode)
{
var allText = await output.Content.ReadAsStringAsync();
var fields = JsonConvert.DeserializeObject<List<FirstName>>(allText);
var test = JsonConvert.SerializeObject(fields);
Console.WriteLine(test);
int total_FirstName = 0;
int total_noFirstName = 0;
int total = total_FirstName + total_noFirstName;
foreach (var split in fields)
{
if (split.Name_First != null)
{
total_FirstName++;
}
else if (split.Name_First == null)
{
total_noFirstName++;
}
}
Console.WriteLine(total);
Console.WriteLine(total_noFirstName);
Console.WriteLine(total_FirstName);
}
else
{
// Error code returned
Console.WriteLine("No records found on second method.");
}
}
}
}
}
Class Properties
int ReportID {get; set;}
Class FirstName
string Name_First {get; set;}
Results at Console.WriteLine(test);
[{"Name_First":"Mario"}]
[{"Name_First":null}]
Results for Console.WriteLine(allText);
[{"Name_First":"Mario","Entry_ID":"72313"}]
[{"Name_Last":"Rincon Recio","Entry_ID":"72313"}]
If you want to keep a count of things in a loop, you need to declare the counter variables outside of the loop, otherwise they get reinitialised. Using your data from the question, the relevant loop should look like this:
List<string> allTexts = new List<string>
{ #"[{""Name_First"":""Mario"",""Entry_ID"":""72313""}]",
#"[{""Name_Last"":""Rincon Recio"",""Entry_ID"":""72313""}]" };
int total_FirstName = 0;
int total_noFirstName = 0;
foreach(var allText in allTexts)
{
var fields = JsonConvert.DeserializeObject<List<FirstName>>(allText);
var test = JsonConvert.SerializeObject(fields);
Console.WriteLine(test);
foreach (var split in fields)
{
if (split.Name_First != null)
{
total_FirstName++;
}
else
{
total_noFirstName++;
}
}
}
int total = total_FirstName + total_noFirstName;
Console.WriteLine(total);
Console.WriteLine(total_noFirstName);
Console.WriteLine(total_FirstName);
Output:
[{"Name_First":"Mario"}]
[{"Name_First":null}]
2
1
1
I wish to automate the transfer of ether to a list of people.
Assume the list is in a csv.
I wrote some code to automate the process.
class Program
{
int nonce = 0;
static void Main(string[] args)
{
var account = SetupAccount();
var recipients = ReadCsv();
var web3Init = GetConnection();
nonce = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(account.Address).Result;
//var recipients = new List<Records>() { new Records() { Value = 10000000000000000, Address = "0x5CC494843e3f4AC175A5e730c300b011FAbF2cEa" } };
foreach (var recipient in recipients)
{
try
{
var web3 = GetConnection();
var receipt = SendEther(account, recipient, web3).Result;
}
catch (System.Exception)
{
MessageBox.Show("Failed");
}
Thread.Sleep(30000);
}
}
private static async Task<TransactionReceipt> SendEther(Account account, Records recipient, Web3 web3)
{
var transactionPolling = web3.TransactionManager.TransactionReceiptService;
//var currentBalance = await web3.Eth.GetBalance.SendRequestAsync(account.Address);
//assumed client is mining already
//when sending a transaction using an Account, a raw transaction is signed and send using the private key
return await transactionPolling.SendRequestAndWaitForReceiptAsync(() =>
{
var transactionInput = new TransactionInput
{
From = account.Address,
//Gas = new HexBigInteger(25000),
GasPrice = new HexBigInteger(10 ^ 10),
To = recipient.Address,
Value = new HexBigInteger(new BigInteger(recipient.Value)),
Nonce = nonce
};
var txSigned = new Nethereum.Signer.TransactionSigner();
var signedTx = txSigned.SignTransaction(account.PrivateKey, transactionInput.To, transactionInput.Value, transactionInput.Nonce);
var transaction = new Nethereum.RPC.Eth.Transactions.EthSendRawTransaction(web3.Client);
nonce++;
return transaction.SendRequestAsync(signedTx);
});
}
private static Web3 GetConnection()
{
return new Web3("https://mainnet.infura.io");
}
private static Account SetupAccount()
{
var password = "#Password";
var accountFilePath = #"filePath";
return Account.LoadFromKeyStoreFile(accountFilePath, password);
}
private static List<Records> ReadCsv()
{
string filePath = #"C:\Users\Potti\source\repos\ConversionFiles\XrcfRecipients.csv";
if (File.Exists(filePath))
{
using (StreamReader stream = new StreamReader(filePath))
{
CsvReader reader = new CsvReader(stream, new Configuration
{
TrimOptions = TrimOptions.Trim,
HasHeaderRecord = true,
HeaderValidated = null
});
reader.Configuration.RegisterClassMap<RecordMapper>();
return reader.GetRecords<Records>().ToList();
}
}
else
{
return null;
}
}
}
class Records
{
public string Address { get; set; }
public decimal Value { get; set; }
}
sealed class RecordMapper : ClassMap<Records>
{
public RecordMapper()
{
Map(x => x.Address).Name("Address");
Map(x => x.Value).Name("Value");
}
}
How do i modify the process to execute all the transactions at once instead of waiting for each to complete? (Fire and forget)
Also, are there any security considerations of doing this?
What you are currently doing is waiting for each transaction to be mined. What you can do is the following:
var account = new Account("privateKey"); // or load it from your keystore file as you are doing.
var web3 = new Web3(account, "https://mainnet.infura.io");
First create a web3 instance using the same Account object, because we are using an account with a private key, Nethereum will sign your transactions offline before sending them.
Now using the TransactionManager, you can then send a transaction per each recepient
var transactionHashes = new List<string>();
foreach(var recepient in recepients){
var transactionInput = new TransactionInput
{
From = account.Address,
GasPrice = Web3.Convert.ToWei(1.5, UnitConversion.EthUnit.Gwei);,
To = recipient.Address,
Value = new HexBigInteger(new BigInteger(recipient.Value)),
};
var transactionHash = web3.Eth.TransactionManager.SendTransactionAsync(transactionInput);
transanctionHashes.Add(transactionHash);
}
Note that when Nethereum uses the same instance of an Account and TransactionManager (or Web3 in this scenario) it creates a default NonceMemoryService, so you don't need to keep track of your nonces (Transaction number), to sign the transaction.
Also I have done a conversion for the GasPrice from Gwei to Wei, as an example of Unit conversions, I assume that you already have converted to Wei the Ether amounts you are going to send.
Finally, another note, to further simplify this, there is an upcoming EtherTransferService which allows you to input Ether amounts and Gwei price amounts to avoid doing conversions. Also the gas price will be calculated for you, if not passed any parameter.
web3.Eth.GetEtherTransferService().TransferEtherAsync("toAddress", EtherAmount);
Hello i'm trying to implement Facebook friends tracker that can track who's online and who's offline using Selenium WebDriver.
Everything works fine except when a user goes offline and i remove him from the collection it throws an exception .
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Support.Events;
using OpenQA.Selenium.Support.UI;
namespace FacebookFriendTracker
{
public delegate void StatusChangedEventHandler(object source, StatusChangedEventArgs e);
public class Watcher
{
private readonly IWebDriver _driver;
private HashSet<User> _tempUserOnline = new HashSet<User>();
public event StatusChangedEventHandler StatusChanged;
private bool run;
public Watcher()
{
//UsersOnline = new HashSet<User>();
_driver = new EdgeDriver();
_driver.Navigate().GoToUrl("https://mbasic.facebook.com");
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(20));
wait.Until(ExpectedConditions.ElementExists(By.PartialLinkText("Chat")));
var element = _driver.FindElement(By.PartialLinkText("Chat"));
element.Click();
run = false;
}
public void Tracker()
{
Thread.Sleep(5000);
//_usersOnline = _driver.FindElements(By.XPath(".//a[contains(#href,'fbid')]"));
var usersOnline = new HashSet<User>();
foreach (var userOnline in _driver.FindElements(By.XPath(".//a[contains(#href,'fbid')]")))
{
var extracedAttributeValue = userOnline.GetAttribute("href");
var regex = new Regex(#"\d+");
var id = long.Parse(regex.Match(extracedAttributeValue).Value);
var fullName = userOnline.Text;
usersOnline.Add(new User() {FullName = fullName, Id = id});
}
while (true)
{
Thread.Sleep(5000);
_driver.Navigate().Refresh();
var newUsersOnline = new HashSet<User>();
foreach (var user in _driver.FindElements(By.XPath(".//a[contains(#href,'fbid')]")))
{
var attirbute = user.GetAttribute("href");
var reg = new Regex(#"\d+");
var newId = long.Parse(reg.Match(attirbute).Value);
var newFullName = user.Text;
newUsersOnline.Add(new User() { FullName = newFullName, Id = newId });
}
_tempUserOnline = usersOnline;
foreach (var usrOnline in newUsersOnline.Except(_tempUserOnline))
{
OnStatusChanged(this , new StatusChangedEventArgs() {User = usrOnline,Status = Status.Online});
_tempUserOnline.Add(usrOnline);
}
// Here it throws and exception if the the user goes offline
foreach (var usroffline in usersOnline.Except(newUsersOnline))
{
OnStatusChanged(this, new StatusChangedEventArgs() { User = usroffline, Status = Status.Offline });
_tempUserOnline.Remove(usroffline);
}
}
}
protected virtual void OnStatusChanged(object source, StatusChangedEventArgs e)
{
if (StatusChanged !=null)
OnStatusChanged(source,e);
else
{
Console.WriteLine("User: {0} is {1} ",e.User.FullName,e.Status);
}
}
}
}
You cannot modify that collection while an emueration is in progress. A common fix is to ToList() it:
foreach (var usroffline in usersOnline.Except(newUsersOnline).ToList())
{
OnStatusChanged(this, new StatusChangedEventArgs() { User = usroffline, Status = Status.Offline });
_tempUserOnline.Remove(usroffline);
}
The problem is that you can't modify a collection while you're iterating through it with a foreach loop.
Here, you are setting _tempUserOnline to point to the same object as usersOnline:
_tempUserOnline = usersOnline;
Then further down, you have this loop, which modifies _tempUserOnline, thus modifying usersOnline also since they point to the same object. Since you are looping through usersOnline (and effectively _tempUserOnline too), you are getting the error when you do _tempUserOnline.Remove(usroffline).
foreach (var usroffline in usersOnline.Except(newUsersOnline))
{
OnStatusChanged(this, new StatusChangedEventArgs() { User = usroffline, Status = Status.Offline });
_tempUserOnline.Remove(usroffline);
}
Did you intend to copy usersOnline into tempUserOnline? If so, you should use .CopyTo() or something equivalent to make an actual copy:
User[] _tempUserOnline = new User[usersOnline.Count - 1];
usersOnline.CopyTo(_tempUserOnline);