I am trying to use Xamarin Forms to create a cross-platform application. I have decided to use Firestore as the database for my app.
I am trying to add chat functionality to my app using and I'm struggling with implementing the real-time listening functionality. I have already created a ViewModel class containing an ObservableCollection of Chats that is used by a ListView in the UI.
public class ChatsVM
{
public ObservableCollection<Chat> Chats { get; set; }
public ChatsVM()
{
Chats = new ObservableCollection<Chat>();
ReadMessages();
}
public async void ReadMessages()
{
User currentUser = await DependencyService.Get<IFirebaseAuthenticationService>().GetCurrentUserProfileAsync();
IList<Chat> chatList = await DependencyService.Get<IChatService>().GetChatsForUserAndListenAsync(currentUser.IsLandlord, currentUser.Id);
foreach (var chat in chatList)
{
Chats.Add(chat);
}
}
}
I have also created the services to fetch the data from Firestore. On the service side of things (example is showing Android service) I am using a standard List to hold Chat objects
List<Chat> Chats;
bool hasReadChats;
The method GetChatsForUserAndListenAsync adds a snapshot listener to my query and passes events to the OnEvent method.
public async Task<IList<Chat>> GetChatsForUserAndListenAsync(bool isLandlord, string userId)
{
string fieldToSearch;
if (isLandlord)
{
fieldToSearch = "landlordId";
}
else
{
fieldToSearch = "tenantId";
}
try
{
// Reset the hasReadChats value.
hasReadChats = false;
CollectionReference collectionReference = FirebaseFirestore.Instance.Collection(Constants.Chats);
// Get all documents in the collection and attach a OnCompleteListener to
// provide a callback function.
collectionReference.WhereEqualTo(fieldToSearch, userId).AddSnapshotListener(this);
// Wait until the callback has finished reading and formatting the returned
// documents.
for (int i = 0; i < 10; i++)
{
await System.Threading.Tasks.Task.Delay(100);
// If the callback has finished, continue rest of the execution.
if (hasReadChats)
{
break;
}
}
return Chats;
}
catch (FirebaseFirestoreException ex)
{
throw new Exception(ex.Message);
}
catch (Exception)
{
throw new Exception("An unknown error occurred. Please try again.");
}
}
public void OnEvent(Java.Lang.Object value, FirebaseFirestoreException error)
{
var snapshot = (QuerySnapshot) value;
if (!snapshot.IsEmpty)
{
var documents = snapshot.Documents;
Chats.Clear();
foreach (var document in documents)
{
Chat chat = new Chat
{
Id = document.Id,
LandlordId = document.Get("landlordId") != null ? document.Get("landlordId").ToString() : "",
TenantId = document.Get("tenantId") != null ? document.Get("tenantId").ToString() : ""
};
//JavaList messageList = (JavaList) document.Get("messages");
//List<Message> messages = new List<Message>();
// chat.Messages = messages;
Chats.Add(chat);
}
hasReadChats = true;
}
}
How would I propagate any changes to this list that are made by the event handler to the ObservableCollection in my VM class?
Credit to Jason for suggesting this answer.
So I modified the event handler to look for document changes in Firestore. Based on these changes, the service would send different messages using MessagingCenter.
public async void OnEvent(Java.Lang.Object value, FirebaseFirestoreException error)
{
var snapshot = (QuerySnapshot) value;
if (!snapshot.IsEmpty)
{
var documentChanges = snapshot.DocumentChanges;
IFirebaseAuthenticationService firebaseAuthenticationService = DependencyService.Get<IFirebaseAuthenticationService>();
Chats.Clear();
foreach (var documentChange in documentChanges)
{
var document = documentChange.Document;
string memberOne = document.Get(Constants.MemberOne) != null ? document.Get(Constants.MemberOne).ToString() : "";
string memberTwo = document.Get(Constants.MemberTwo) != null ? document.Get(Constants.MemberTwo).ToString() : "";
Chat chat = new Chat
{
Id = document.Id,
MemberOne = memberOne,
MemberTwo = memberTwo,
};
var documentType = documentChange.GetType().ToString();
switch (documentType)
{
case Constants.Added:
MessagingCenter.Send<IChatService, Chat>(this, Constants.Added, chat);
break;
case Constants.Modified:
MessagingCenter.Send<IChatService, Chat>(this, Constants.Modified, chat);
break;
case Constants.Removed:
MessagingCenter.Send<IChatService, Chat>(this, Constants.Removed, chat);
break;
default:
break;
}
Chats.Add(chat);
}
hasReadChats = true;
}
}
In the constructor for the VM, I subscribed as a listener for these messages and updated the ObservableCollection accordingly.
public class ChatsVM
{
public ObservableCollection<Chat> Chats { get; set; }
public ChatsVM()
{
Chats = new ObservableCollection<Chat>();
ReadChats();
}
public async void ReadChats()
{
IFirebaseAuthenticationService firebaseAuthenticationService = DependencyService.Get<IFirebaseAuthenticationService>();
MessagingCenter.Subscribe<IChatService, Chat>(this, Constants.Added, (sender, args) =>
{
Chat chat = args;
Chats.Add(chat);
});
MessagingCenter.Subscribe<IChatService, Chat>(this, Constants.Modified, (sender, args) =>
{
Chat updatedChat = args;
Chats.Any(chat =>
{
if (chat.Id.Equals(updatedChat.Id))
{
int oldIndex = Chats.IndexOf(chat);
Chats.Move(oldIndex, 0);
return true;
}
return false;
});
});
MessagingCenter.Subscribe<IChatService, Chat>(this, Constants.Removed, (sender, args) =>
{
Chat removedChat = args;
Chats.Any(chat =>
{
if (chat.Id.Equals(removedChat.Id))
{
Chats.Remove(chat);
return true;
}
return false;
});
});
User currentUser = await firebaseAuthenticationService.GetCurrentUserProfileAsync();
await DependencyService.Get<IChatService>().GetChatsForUserAndListenAsync(currentUser.Id);
}
}
Related
Trying to create kind of market scanner. Code below is supposed to return chain of option contracts. Call to TWS API is an async method that returns some data only if I get ContractEnd or Error response from TWS. On the first call to reqContractDetails() it works as expected, I get list of contracts, receive message "ContractEnd", and exit from the method.
Obstacle
In some reason, on the second call to reqContractDetails() I don't get any notification from TWS. I have to stop and restart my application, initiating new connection to the server to make it working again.
Update
After refactoring my code I'm getting an error on a second call that says "Unable to read beyond the end of the stream". Call stack looks this way.
IBLibrary.dll!IBLibrary.OptionService.GetOptionsChain.AnonymousMethod__3(IBLibrary.Messages.ErrorMessage data) Line 64
IBLibrary.dll!IBLibrary.Classes.Client.error(string str) Line 42
CSharpAPI.dll!IBApi.EReader.putMessageToQueue() Line 94
CSharpAPI.dll!IBApi.EReader.Start.AnonymousMethod__9_0() Line 48
My implementation of the wrapper in C#
public class BaseService : IDisposable
{
protected Client Sender { get; set; }
protected EReader Receiver { get; set; }
public BaseService()
{
Sender = new Client();
Sender.Socket.eConnect("127.0.0.1", 7496, 0);
Receiver = new EReader(Sender.Socket, Sender.Signal);
Receiver.Start();
var process = new Thread(() =>
{
while (Sender.Socket.IsConnected())
{
Sender.Signal.waitForSignal();
Receiver.processMsgs();
}
})
{
IsBackground = true
};
process.Start();
}
public void Dispose()
{
Sender.Socket.eDisconnect();
}
}
public class OptionService : BaseService
{
public Task<List<OptionModel>> GetOptionsChain(OptionModel query)
{
if (query == null)
{
query = new OptionModel();
}
var process = Task.Run(() =>
{
var done = false;
var id = new Random(DateTime.Now.Millisecond).Next();
var contract = new Contract
{
Symbol = query.Symbol,
SecType = "OPT",
Exchange = "SMART",
Currency = "USD",
LastTradeDateOrContractMonth = query.Expiration
};
var contracts = new List<OptionModel>();
Action<ErrorMessage> errorMessage = null;
Action<ContractDetailsMessage> contractMessage = null;
Action<ContractDetailsEndMessage> contractMessageEnd = null;
contractMessage = (ContractDetailsMessage data) =>
{
contracts.Add(new OptionModel
{
Symbol = data.ContractDetails.Contract.Symbol,
Right = data.ContractDetails.Contract.Right,
Strike = data.ContractDetails.Contract.Strike,
Expiration = data.ContractDetails.RealExpirationDate
});
};
// I receive this message at first, but not the second time
contractMessageEnd = (ContractDetailsEndMessage data) =>
{
done = true;
};
errorMessage = (ErrorMessage data) =>
{
var notifications = new List<int>
{
(int) ErrorCode.MarketDataFarmConnectionIsOK,
(int) ErrorCode.HmdsDataFarmConnectionIsOK
};
if (notifications.Contains(data.ErrorCode) == false)
{
done = true;
}
};
Sender.ErrorEvent += errorMessage;
Sender.ContractDetailsEvent += contractMessage;
Sender.ContractDetailsEndEvent += contractMessageEnd;
Sender.Socket.reqContractDetails(id, contract);
// Execute method until we get all contracts
// The econd call to reqContractDetails doesn't return
// any notification, so obviously this line hangs forever
while (done == false);
Sender.ErrorEvent -= errorMessage;
Sender.ContractDetailsEvent -= contractMessage;
Sender.ContractDetailsEndEvent -= contractMessageEnd;
return contracts;
});
return process;
}
}
As far as nobody has the answer, even IB itself, the only solution that I see is, to convert my API controller to a synchronous controller and close socket connection to IB server after every request.
Old version.
public class ServiceOptionsController : BaseServiceController
{
OptionService Service = new OptionService();
[AcceptVerbs("POST")]
public async Task<List<OptionModel>> Options([FromBody] dynamic data)
{
var selectors = data.ToObject<QueryModel>();
var optionModel = new OptionModel
{
Symbol = "MSFT",
Expiration = "201806"
};
var processes = new List<Task<List<OptionModel>>>
{
Service.GetOptionsChain(optionModel)
};
return (await Task.WhenAll(processes)).SelectMany(o => o).ToList();
}
}
Working version.
public class ServiceOptionsController : BaseServiceController
{
[AcceptVerbs("POST")]
public List<OptionModel> Options([FromBody] dynamic data)
{
var selectors = data.ToObject<QueryModel>();
var optionModel = new OptionModel
{
Symbol = "MSFT",
Expiration = "201806"
};
var optionService = new OptionService();
var processes = new List<Task<List<OptionModel>>>
{
optionService.GetOptionsChain(optionModel)
};
var items = Task.WhenAll(processes).Result.SelectMany(o => o).ToList();
optionService.Dispose(); // Ridiculous fix for ridiculous API
return items;
}
}
// The class for the search query
public class SearchQueries
{
List<data> list = new List<data>();
string response;
// The method that return the list after it is set
public List<data> GetData()
{
return list;
}
// The method that do the searching from the google API service
public async void SetData()
{
// The problem starts here, when i instantiate the search class in this class in other to get the value of the text in the autosuggestbox for my query, it crashes whenever i try to launch the page. it works fine whenever i give the address default data e.g string address = "London", the page open when i launch it and give me London related result whenever i type in the autosuggestbox.
Search search = new Search();
string address = search.Address;
list.Clear();
// Note the tutorial i used was getting the data from a local folder, but i'm trying to get mine from Google API
string dataUri = "https://maps.googleapis.com/maps/api/place/autocomplete/json?key=AIzaSyDBazIiBn2tTmqcSpkH65Xq5doTSuOo22A&input=" + address;
string Api = System.Uri.EscapeDataString(dataUri);
HttpClient client = new HttpClient();
client.Timeout = TimeSpan.FromMilliseconds(1000);
try
{
response = await client.GetStringAsync(Api);
for (uint i = 0; i < jsonarray.Count; i++)
{
string json_string_object = jsonarray.GetObjectAt(i)["description"].ToString();
list.Add(new data() { name = json_string_object });
}
}
catch (TimeoutException e)
{
ContentDialog myDlg = new ContentDialog()
{
PrimaryButtonText = "OK"
};
myDlg.Content = e.ToString();
}
}
// Method to get matched data
public IEnumerable<data> getmatchingCustomer(string query)
{
return list.Where(c => c.name.IndexOf(query, StringComparison.CurrentCultureIgnoreCase) > -1).OrderByDescending(c => c.name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase));
}
// constructor for returning the SetData() method
public SearchQueries()
{
// It points to this method whenever the application crash, with the notification of infinite loop or infinite recursion
SetData();
}
}
// The Main Class of the page
public sealed partial class Search : Page
{
public string theaddress { get; set; }
SearchQueriess queries = new SearchQueriess();
public Search()
{
this.InitializeComponent();
myMap.Loaded += MyMap_Loaded;
theaddress = locationAddress.Text;
}
// The text change method of the autosuggest box.
private async void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
if (sender.Text.Length > 1)
{
var marchingData = queries.getmatchingCustomer(sender.Text);
sender.ItemsSource = marchingData.ToList();
}
else
{
sender.ItemsSource = new string[] { "No suggestion...." };
}
}
}
}
I have an ASP.NET Web API with SQL Database / Entity Framework that I will be using as the server side of an application that I am building. The client side will be WPF for now. I am having some trouble determining the best method for the MVVM controls to access the data from the server. All of the tutorials and courses I have done for MVVM connect directly to a local database, and they update when the control is loaded. What I want is a constant loop where the API is called once per minute and the data in the control automatically refreshed at that point. I have this working for the two main controls (ran into a problem updating the collection from a different thread and had to use App.Current.Dispatcher.Invoke), however I am not sure if I am keeping to best practice running the API calls directly from the view model. Also, I need to be able to share data between the view models.
ScheduledForwardsViewModel has data that ActiveForwardsViewModel needs, and ActiveForwardsViewModel has data that CreateForwardsViewModel needs. The ActiveForward for each entry needs to be able to see the scheduled date for each corresponding scheduled item so it can find the next event and display the time left until that event occurs in the ActiveForwardsView. The CreateForwardView simply needs access to the AllUsers,AllGroups,AllContacts observable collections created during the ActiveForwards data manipulation so it can use that data in the drop down fields when scheduling a new forward. I haven't been able to get the dropdowns to work yet. I do have access to the info I need from ScheduledForwardsView using a static object, but I feel like there may be a better way. I would prefer to refrain from making multiple calls to the API for the same data when part of my app already has the data I need.
Basic Layout:
MainWindow
CreateForwardView (with view model)
[Drop down boxes to select user and schedule forward]
[Buttons, etc.]
[TabControl]
[TabItem: Active Forwards]
ActiveForwardsView (with view model)
[TabItem: Scheduled Forwards]
ScheduledForwardsView (with view model)
[EndTabControl]
The ActiveForwardsViewModel is below. The ScheduledForwardsViewModel is essentially the same thing, but it calls a different API method and contains a static object that I am using in ActiveForwardsViewModel (see the part where I wait for ScheduledForwardsViewModel to complete 1 run before I continue with UpdateUserObjects). That didn't seem like the right way to do it, but I set it up that way just so I could move onto other things.
public class ActiveForwardsViewModel : INotifyPropertyChanged
{
private CancellationToken updater = default(CancellationToken);
private AppLog log;
private FFObject _selectedObject;
private ObservableCollection<FFObject> _activeForwards;
public ObservableCollection<FFObject> ActiveForwards
{
get { return _activeForwards; }
set
{
if (_activeForwards != value)
{
_activeForwards = value;
PropertyChanged(this, new PropertyChangedEventArgs("ActiveForwards"));
}
}
}
public ObservableCollection<FFObject> AllUsers { get; set; }
public ObservableCollection<FFObject> AllGroups { get; set; }
public ObservableCollection<FFObject> AllContacts { get; set; }
public ObservableCollection<FFObject> AllObjects { get; set; }
public bool Running = false;
public bool FirstRunComplete = false;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ActiveForwardsViewModel()
{
if (DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
{
updater.ThrowIfCancellationRequested();
return;
}
ActiveForwards = new ObservableCollection<FFObject>();
AllUsers = new ObservableCollection<FFObject>();
AllGroups = new ObservableCollection<FFObject>();
AllContacts = new ObservableCollection<FFObject>();
AllObjects = new ObservableCollection<FFObject>();
StartUpdater(updater);
RemoveForwardCommand = new RelayCommand(OnRemove, CanRemove);
}
public async Task StartUpdater(CancellationToken token = default(CancellationToken))
{
while (!token.IsCancellationRequested)
{
Running = true;
await this.Update();
FirstRunComplete = true;
try
{
await Task.Delay(TimeSpan.FromMinutes(1), token);
}
catch (TaskCanceledException)
{
Running = false;
break;
}
}
}
private List<FFObject> UpdateAllObjectList(string strJson)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<List<FFObject>>(strJson);
//return AllObjects;
}
public string GetString(string method)
{
using (var client = new WebClient())
{
var url = string.Format("https://mywebapi.domain.com/api/{0}", method);
return client.DownloadString(url);
}
}
public async Task Update()
{
Func<string, List<FFObject>> getUsersJson = UpdateAllObjectList;
await Task<string>.Factory.StartNew(() => GetString("Users"))
.ContinueWith(antecendent => getUsersJson(antecendent.Result))
.ContinueWith(antecendent => UpdateUserObjects(antecendent.Result));
}
private void CheckRemoved(List<FFObject> all)
{
List<FFObject> RemoveObjects = new List<FFObject>();
foreach (var obj in ActiveForwards)
{
var newObj = all.FirstOrDefault(i => i.ID == obj.ID);
if (newObj == null)
{
RemoveObjects.Add(obj);
}
else
{
if (!(bool) newObj.IsForwarded)
{
RemoveObjects.Add(obj);
}
}
}
foreach (var obj in RemoveObjects)
{
App.Current.Dispatcher.Invoke((Action)delegate
{
ActiveForwards.Remove(obj);
});
}
}
private void UpdateUserObjects(List<FFObject> all)
{
Debug.WriteLine("Starting UpdateUserObject");
if (all != null)
{
AllObjects = new ObservableCollection<FFObject>(all);
CheckRemoved(all);
var x = 0;
while (!ScheduledForwardsViewModel.RunOnce && x < 5)
{
System.Threading.Thread.Sleep(1000);
x++;
}
foreach (var obj in all)
{
if (obj.ObjectType.ToLower() == "user")
{
var existing = AllUsers.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//AllUsers.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
AllUsers.Add(obj);
});
}
if (obj.IsForwarded ?? false)
{
existing = ActiveForwards.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
obj.TimeLeft = "";
var now = DateTime.Now;
var TimeRemaining = new TimeSpan?();
foreach (var schedule in ScheduledForwardsViewModel.ScheduledForwards)
{
if (schedule.DistinguishedName == obj.DistinguishedName)
{
if (schedule.StopJobStatus == "Scheduled")
{
if (TimeRemaining == null)
{
TimeRemaining = schedule.StopTime - now;
}
else
{
if (schedule.StopTime - now < TimeRemaining)
{
TimeRemaining = schedule.StopTime - now;
}
}
}
}
}
if (TimeRemaining != null)
{
var remaining = (TimeSpan)TimeRemaining;
var min = new int();
if (remaining.Seconds > 30)
{
min = remaining.Minutes + 1;
}
double m = remaining.Minutes / (double)60;
double hm = remaining.Hours + m;
double h = hm / 24;
double dh = remaining.Days + h;
if (remaining.Days > 0)
{
var daysleft = Math.Round(dh, 2);
var quarterRound = Math.Round(dh * 4, MidpointRounding.ToEven) / 4;
obj.TimeLeft = quarterRound + "d";
}
else if (remaining.Hours > 0)
{
var hoursleft = Math.Round(hm, 2);
var quarterRound = Math.Round(hm * 4, MidpointRounding.ToEven) / 4;
obj.TimeLeft = quarterRound + "h";
obj.TimeLeft = remaining.Hours + "h" + remaining.Minutes + "m";
}
else
{
if (min == 0)
{
obj.TimeLeft = "< 30s";
}
else
{
obj.TimeLeft = min + "m";
}
}
}
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//ActiveForwards.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
ActiveForwards.Add(obj);
});
}
}
}
else if (obj.ObjectType.ToLower() == "group")
{
if (obj.IsForwarded ?? false)
{
var existing = AllGroups.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//AllGroups.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
AllGroups.Add(obj);
});
}
}
}
else if (obj.ObjectType.ToLower() == "contact")
{
if (obj.IsForwarded ?? false)
{
var existing = AllContacts.FirstOrDefault(i => i.DistinguishedName == obj.DistinguishedName);
if (existing != null)
{
existing.ForwardedTo = obj.ForwardedTo;
existing.ForwardedToAd = obj.ForwardedToAd;
existing.ForwardedToDn = obj.ForwardedToDn;
existing.ForwardedToPk = obj.ForwardedToPk;
existing.ForwardedToDisplay = obj.ForwardedToDisplay;
existing.IsForwarded = obj.IsForwarded;
existing.DeliverAndRedirect = obj.DeliverAndRedirect;
existing.IsScheduled = obj.IsScheduled;
existing.TimeLeft = obj.TimeLeft;
}
else
{
//AllContacts.Add(obj);
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
AllContacts.Add(obj);
});
}
}
}
else
{
throw new NotImplementedException();
}
}
}
RunOnce = true;
}
Hello,
I'm having trouble getting my class to call a Moq'd method. My situation is thus:
public class ResetPasswordsTask : IRefreshTimeTask
{
public long ExecutionId => 2100;
public bool Enabled => true;
public Dictionary<string, object> Params { get; set; }
public KeyValuePair<string, Type>[] RequiredParams => new[]
{
new KeyValuePair<string, Type>("targetConfigs", typeof(InMemoryConfiguration))
};
public ILogger Logger { get; set; }
internal IConfiguration SandboxConfig;
internal IPartnerService PartnerService;
internal ISalesForceBulkDataInserter DataInserter;
public void Execute()
{
SandboxConfig = (IConfiguration)Params["targetConfigs"];
PartnerService = Login.SalesforceApiLogin(true, SandboxConfig);
DataInserter = new SalesForceBulkDataInserter();
//InitialiseImapClient();
// Retrieve users
var users = TestAutomation.Salesforce.Pages.Base.User.GetUsers(PartnerService, SandboxConfig.Refresh_Usernames);
// Upsert emails
var emailUpsertResults = UpsertEmails(users, SandboxConfig.IMAP_Email);
// Hit mailbox and visit confirmation links
var activatedUsers = emailUpsertResults.Values.Where(r => r.Status == EmailResetStatusEnum.Success).Select(r => r.User).ToList();
var confirmationLinkResults = ConfirmEmailChanges(activatedUsers);
// Upsert passwords
// All the users, except those for whom email update failed
var passwordUpdateUsers = users.Except(confirmationLinkResults.Values.Where(r => !r.Success).Select(r => r.User)).ToList();
var passwordUpsertResults = UpsertPasswords(passwordUpdateUsers);
// Hit mailbox for new tokens
var completeResetResults = RetrieveTokens(passwordUpsertResults.Values.Where(r => r.Success));
var output = string.Join("\n", completeResetResults.Values.Where(c => c.Success).Select(result => string.Join("\t", new List<string> { result.User.Username, result.Password, result.SecurityToken })));
//Logger.Info(output);
Logger.Info(System.IO.Path.GetDirectoryName("."));
Logger.Info(System.Reflection.Assembly.GetExecutingAssembly().Location);
OpenSslEncryption.EncryptToFile(SandboxConfig.EncryptionPassword, "passwords.enc", output);
}
// Results are keyed by username
internal IDictionary<string, EmailResetResult> UpsertEmails(ICollection<User> users, string newEmail)
{
var results = users.ToDictionary(u => u.Username, u => new EmailResetResult(u));
Logger.Info($"Updating emails for {users.Count} users");
foreach (var user in users)
{
Logger.Info($"updating email for {user.Username} users");
var result = results[user.Username];
// Prevent upserting the profile
if (user.ProfileId != null)
{
Logger.Info("Preventing profile upsert");
user.ProfileId.SalesForceId = user.ProfileId.OriginId;
}
// If the user has had their email set to what we're setting now, they can be skipped
if (user.Email.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase) && user.IsActive)
{
Logger.Info($"User {user.Username} has their email set to {newEmail}; skipping");
result.Status = EmailResetStatusEnum.Skipped;
continue;
}
// Otherwise, set the email and carry on
user.Email = newEmail;
user.IsActive = true;
// dataInserter.Upsert won't produce errors individually, and instead only log faulty upserts
try
{
DataInserter.Upsert(new List<User> { user });
Logger.Info($"Activated user {user.Username}");
result.Status = EmailResetStatusEnum.Success;
}
catch (Exception e)
{
var error = $"Failed to update the email for user {user.Username} to {newEmail}. Error details: {e}";
Logger.Error(TestAutomation.Framework.Core.Logger.Logger.FormatJson(error));
result.ErrorMessage = e.ToString();
result.Status = EmailResetStatusEnum.Failure;
}
}
return results;
}
internal IDictionary<string, Result> ConfirmEmailChanges(ICollection<User> users)
{
var results = users.ToDictionary(u => u.Username, u => new Result(u));
// Ran as a task to have a timeout
Task.Run(() => ConfirmEmailChangesTask(results, users)).Wait(TimeSpan.FromMinutes(users.Count * 5));
return results;
}
internal void ConfirmEmailChangesTask(IDictionary<string, Result> results, ICollection<User> users)
{
var remainingUsers = new HashSet<User>(users);
while (true)
{
// Wait a bit; either for the emails to come in, or to give the webserver breathing room
Thread.Sleep(new TimeSpan(0, 0, 15));
Logger.Info($"Opening mailbox for {SandboxConfig.IMAP_Email}");
using (var imapClient = CreateImapClient())
{
var messages = imapClient.SearchMessages(SearchQuery.NotSeen.And(SearchQuery.DeliveredAfter(DateTime.Now.AddHours(-1))));
Logger.Info($"Found {messages.Count} messages");
var remainingUsersCopy = new HashSet<User>(remainingUsers);
Logger.Info($"Attempting to confirm emails for {remainingUsers.Count} users");
foreach (var user in remainingUsersCopy)
{
Logger.Info("Attempting to confirm email change for " + user.Username);
foreach (var message in messages.Where(m => m.MimeMessage.TextBody.Contains(user.Username) && m.MimeMessage.Subject.Contains("Sandbox: Finish changing your Salesforce")))
{
Logger.Info("Message found");
var confirmLink = GetEmailConfirmationLink(message);
if (confirmLink == null) continue;
// Visit the URL
var request = WebRequest.Create(confirmLink);
request.Timeout = (int)TimeSpan.FromSeconds(20).TotalMilliseconds;
var result = results[user.Username];
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
var statusCode = response.StatusCode;
if (statusCode != HttpStatusCode.OK)
{
var error = $"Failed to load the email change confirmation link: {confirmLink}. HTTP Response: ({statusCode})";
Logger.Error(TestAutomation.Framework.Core.Logger.Logger.FormatJson(error));
result.Success = false;
result.ErrorMessage = error;
}
}
}
catch (WebException e)
{
Logger.Error($"Request failed: {e.Message}\nWill retry later");
continue;
}
result.Success = true;
remainingUsers.Remove(user);
imapClient.MarkAsRead(message);
//Break down mailbox checks
Thread.Sleep(new TimeSpan(0, 0, 1));
}
}
}
if (!remainingUsers.Any())
break;
}
}
#region MailboxInteraction
internal static string GetEmailConfirmationLink(Message message)
{
// Extract confirmation URL
var confirmLinkMatch = Regex.Match(message.MimeMessage.TextBody, #"([a-z]+:\/\/.*\.salesforce\.com\/\S*)");
return !confirmLinkMatch.Success ? null : confirmLinkMatch.Groups[1].Value;
}
internal static string GetSecurityToken(Message message)
{
var tokenMatch = Regex.Match(message.MimeMessage.TextBody, #"Security token \(case-sensitive\): (?<token>\w+)");
return !tokenMatch.Success ? null : tokenMatch.Groups[1].Value;
}
internal virtual IMailClient CreateImapClient()
{
return new IMAPClient(SandboxConfig.IMAP_Username, SandboxConfig.IMAP_Password, SandboxConfig.IMAP_URL);
}
#endregion
}
Test class:
[TestFixture]
public class WhenResettingUserPasswords
{
private const string ConfirmationLink = "test://testdomain.salesforce.com/test/";
[OneTimeSetUp]
public void WebRequestSetup()
{
WebRequest.RegisterPrefix("test", TestableWebRequestCreateFactory.GetFactory());
var uri = new Uri("test://testdomain.salesforce.com/test/");
var expectedRequest = new TestableWebRequest(uri);
expectedRequest.EnqueueResponse(HttpStatusCode.OK, "Success", "Even more success!", false);
TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);
}
private static SetupBag Setup()
{
var bag = new SetupBag
{
Logger = new InMemoryLogger(),
EmailConfirmationLink = ConfirmationLink,
SecurityToken = "TheSecurityToken",
Environment = "EnvName",
EnvironmentUrl = "http://aaa.bbb.ccc/",
User = new User
{
IsActive = false,
Username = "joe.bloggs#company.com",
Email = "joe.bloggs=company.com#example.com",
OriginId = "ABCDEFGHIJKLMNO"
}
};
var task = new Mock<Tasks.ResetPasswordsTask>(MockBehavior.Strict) { CallBase = true };
task.Object.Logger = bag.Logger;
var confirmMessage = new Message
{
UID = new UniqueId(0),
MimeMessage = new MimeMessage
{
Subject = "Sandbox: Finish changing your Salesforce",
Body = new TextPart("plain") { Text = "Confirm email change for joe.bloggs#company.com: " + bag.EmailConfirmationLink }
}
};
var tokenMessage = new Message
{
UID = new UniqueId(1),
MimeMessage = new MimeMessage
{
Subject = "Sandbox: Your new Salesforce security token",
Body = new TextPart("plain") { Text = "New security token for joe.bloggs#company.com: " + bag.SecurityToken }
}
};
var mailClientMock = new Mock<IMailClient>(MockBehavior.Strict);
mailClientMock.Setup(m => m.SearchMessages(It.IsAny<SearchQuery>())).Returns(new List<Message> { confirmMessage, tokenMessage });
task.Setup(t => t.CreateImapClient()).Returns(() => mailClientMock.Object);
var dataInserterMock = new Mock<ISalesForceBulkDataInserter>();
dataInserterMock.Setup(m => m.Upsert(It.IsAny<List<User>>(), false));
var config = new InMemoryConfiguration
{
IMAP_Email = "test.email#company.com"
};
task.Object.SandboxConfig = config;
bag.Task = task;
return bag;
}
[Test]
public void UpsertEmailsTest()
{
var bag = Setup();
var task = bag.Task;
var output = task.Object.ConfirmEmailChanges(new[] { bag.User });
Assert.IsTrue(output.ContainsKey(bag.User.Username));
Assert.IsTrue(output[bag.User.Username].Success);
Assert.IsEmpty(output[bag.User.Username].ErrorMessage);
Assert.AreEqual(task.Object.SandboxConfig.IMAP_Email, output[bag.User.Username].User.Username);
}
}
Now, the result is that calling task.Object.ConfirmEmailChanges() raises an exception about arguments to new IMAPClient() being null, which should not have been called at all. I could not find anything obviously wrong, but that might just be because I'm not familiar enough with Moq or the way my codebase does testing.
Yes, I know I'm mocking a class I want to test. I know it's a bad idea, and I'm aiming for a redesign. I cannot inject IMailClient as a dependency because it needs to be instantiated anew frequently. I have also seen this sort of technique used in our codebase, and it seemed to work.
If you are mocking a concrete class and not an interface then you must make the method you want to mock virtual.
Edit: in your new code the method is also marked as internal. See this question.
You need to declare your method as virtual or Moq is unable to override it. C# is not like Java - only members that are specifically marked are able to be overridden.
EDIT
The code you've posted should work - here is a Linqpad MVCE I generated based on your code, demonstrating that it works correctly. Therefore, you will need to post your actual code, as it has a bug in it.
void Main()
{
// Arrange
var foo = new Mock<Foo> { CallBase = true };
var bar = new Mock<IBar>();
bar.Setup(b => b.Value).Returns(2);
// setup an IBar mock
foo.Setup(f => f.CreateBar()).Returns(bar.Object);
// Act
var results = foo.Object.DoStuff();
results.Dump(); // Prints "2"
}
public class Foo
{
public int DoStuff()
{
var bar = CreateBar();
return bar.Value;
}
public virtual IBar CreateBar()
{
return new RealBar();
}
}
public interface IBar
{
int Value { get;}
}
public class RealBar : IBar
{
public int Value
{
get { return 1; }
}
}
I have a method, return type is List<User>. In this method I have infinite while loop to accept information from another client over socket. Once a new client accepts, I will add this user into list and continue to listen for new client. I give the construction of the method. It is suppose to accept multiple client. Right now, only allow me to accept one client. Before, we did not set the type as List<User>, then there is no return UserList and whole code works fine with multiple user. After I add a return type of Method, it doesn't works.
public List<User> accept() {
List<User> userList = new List<User>();
while (true) {
Command_Listening_Socket = server.Accept();
int msgLenght = Command_Listening_Socket.Receive(msgFromMobile);// receive the byte array from mobile, and store into msgFormMobile
string msg = System.Text.Encoding.ASCII.GetString(msgFromMobile, 0, msgLenght);// convert into string type
if (msg == "setup") {
my_user = new User();
userList.Add(my_user);
}
return userList;
}
}
If you want to use exactly this solution you can use yield return instead of return statement.
But you will need to iterate over the result of Accept() method outside of it.
But it is good to use event based solution for this type of code structure.
public class Program
{
public static IEnumerable<object> Accept()
{
var userList = new List<object>();
var index = 0;
while (true)
{
var msg = "setup";
if (msg == "setup")
{
var returnUser = new
{
Name = "in method " + index
};
Thread.Sleep(300);
yield return returnUser;
}
index++;
}
}
private static void Main(string[] args)
{
foreach (var acc in Accept())
{
Console.WriteLine(acc.ToString());
}
Console.WriteLine("Press any key to continue.");
Console.ReadLine();
}
}
Just wrap it up
public void accept()
{
List<User> users = new List<User>();
while (true)
{
var user = _accept()
if(user != null)
{
users.Add(user)
}
}
}
public User _accept()
{
User my_user = null;
Command_Listening_Socket = server.Accept();
int msgLenght = Command_Listening_Socket.Receive(msgFromMobile);// receive the byte array from mobile, and store into msgFormMobile
string msg = System.Text.Encoding.ASCII.GetString(msgFromMobile, 0, msgLenght);// convert into string type
if (msg == "setup")
{
my_user = new User();
}
return my_user;
}