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;
}
Related
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);
}
}
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;
}
}
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; }
}
}
The error is raised randomly in my code. There's so stacktrace available
It's more common when when RaisedPropertyChanged is called such as
public IImageProvider IForeground
{
get { return BlendifierEffect.ForegroundSource; }
set
{
if (BlendifierEffect.ForegroundSource != value)
{
BlendifierEffect.ForegroundSource = value;
updateImageBlending();
RaisedPropertyChanged();
}
}
}
So I commented out the RaisedPropertyChanged() method call but it's still being raised once in a while.
The updateImageBlending() is a method which just re-render the effect if any of the blending properties is changed.
private async void updateImageBlending()
{
if (IsRendering)
{
return;
}
if (SelectedPicture == null)
{
return;
}
if (SelectedPicture.Index < 3)
{
return;
}
if (SelectedEffect == null )
{
IImageProcessor noEffect = FilterEffects.FirstOrDefault();
if (noEffect != null)
{
RenderResult renderResult = await imageProcessorRenderer.RenderAsync(noEffect, RenderOption.RenderToSoftwareBitmap | RenderOption.RenderAtPreviewSize);
imageProcessorRenderer.BlendBackground = renderResult.SoftwareBitmap;
IBackground = new SoftwareBitmapImageSource(imageProcessorRenderer.BlendBackground);
}
else
{
return;
}
}
Dispatcher.Dispatch(() => { IsRendering = true; });
SoftwareBitmap softwareBitmap = null;
softwareBitmapRenderer.Size = imageProcessorRenderer.PreviewSize;
softwareBitmapRenderer.RenderOptions = RenderOptions.Cpu;
softwareBitmap = await softwareBitmapRenderer.RenderAsync();
Dispatcher.Dispatch(async () =>
{
WriteableBitmapPreview = await convertPreviewToWriteableBitmap(softwareBitmap, null);
});
Dispatcher.Dispatch(() => { IsRendering = false; });
}
softwareBitmapRender takes a BlendEffect and is initialized in the beginning.
if (BlendifierEffect == null)
{
BlendifierEffect = new BlendEffect();
BlendifierEffect.GlobalAlpha = 0.5d;
BlendifierEffect.BlendFunction = BlendFunction.Normal;
BlendifierEffect.TargetOutputOption = OutputOption.PreserveAspectRatio;
BlendifierEffect.TargetArea = new Rect(0d, 0d, Scale, Scale);
}
if (softwareBitmapRenderer == null)
{
softwareBitmapRenderer = new SoftwareBitmapRenderer(BlendifierEffect);
}
BlendEffect is updated as user change its properties such as
public IImageProvider Mask
{
get { return BlendifierEffect.MaskSource; }
set
{
if (BlendifierEffect.MaskSource != value)
{
BlendifierEffect.MaskSource = value;
updateImageBlending();
}
}
}
What is causing the error and how do I catch it or disable it?
We are using entity framework codefirst approach
I am new to entity framework and I am facing error while trying to do "New transaction is not allowed because there are other threads running in the session.
public class DatabaseBackup : IDataBackup
{
private readonly IMonarchDbContext m_db;
public DatabaseBackup(IMonarchDbContext podb)
{
if (podb == null)
throw new ArgumentNullException("podb");
m_db = podb;
}
public DBBackupHistory GetLatestBackupHistory(DBBackupFrequency backupFrequency = DBBackupFrequency.Periodic)
{
DBBackupHistory result = null;
// get the backup history of the given backuptype and populate the objects
var configId = m_db.DBBackupConfigurations.Where(c => c.ScheduleType == (int)backupFrequency && c.BackupStatus == 1).Distinct().Select(c => c.ConfigurationId).DefaultIfEmpty(-1).First();
if (configId > 0)
{
result = m_db.DBBackupHistorys.Where(b => b.Status == 1 && b.ConfigurationId == configId).OrderByDescending(lb => lb.BackupDatetime).FirstOrDefault();
}
return result;
}
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId);
return result;
}
public void Backup(DBBackupConfiguration config, int fileIndex)
{
Console.WriteLine("Running DB Backup type {0} to device {1}", (DBBackupType)config.BackupType, fileIndex);
m_db.StoredProc.SPBackup(config, fileIndex);
}
I am calling the below methods in another class as follows
private readonly IDataBackup m_dataBackup;
public int PerformBackup(int defaultPollIntervalInMinutes = 15)
{
// polling interval in Minutes
int pollInterval = defaultPollIntervalInMinutes;
int fileIndex = getCurrentDumpFileIndex();
// check for the backup configuration
var configurations = m_dataBackup.GetAllConfiguration();
foreach (var config in configurations)
{
var lastBackup = m_dataBackup.GetLatestBackupHistory(DBBackupFrequency.Weekly);
if (lastBackup == null)
{
m_dataBackup.Backup(config, fileIndex + 1);
break;
}
Here is the Db Context class is as below
public class MonarchDbContext:DbContext,IMonarchDbContext
{
private IStoredProcedure m_storedProc;
private static object m_dbIntializerSet;
public MonarchDbContext(string nameOrConnectionString)
: base( nameOrConnectionString )
{
//-- Set the DB initializer only once.
System.Threading.LazyInitializer.EnsureInitialized( ref m_dbIntializerSet,()=>{
Database.SetInitializer<MonarchDbContext>(null);
//-- Give debug builds a chance to overwrite the above.
_SetInitializerForDebugBuilds();
return new object();
});
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
var csb = new SqlConnectionStringBuilder( this.Database.Connection.ConnectionString );
csb.MultipleActiveResultSets = true;
this.Database.Connection.ConnectionString = csb.ToString();
var objectContext = ( this as IObjectContextAdapter ).ObjectContext;
objectContext.CommandTimeout = 3600;
}
#region Public "Tables"
public IDbSet<DBBackupConfiguration> DBBackupConfigurations { get; set; }
public IDbSet<DBBackupHistory> DBBackupHistorys { get; set; }
public IStoredProcedure StoredProc
{
get
{
return System.Threading.LazyInitializer.EnsureInitialized(ref m_storedProc, () => new BackupStoredProc(this.Database));
}
}
#endregion
please let me know how can i solve the issue.
I found the issue
I need to add toList() at the end of the Linq code and it just worked for me.
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId).ToList();
return result;
}
Just add the List to Ienumerbale types