-
May 18th, 2018, 12:42 AM
#1
Thread Starter
Lively Member
[RESOLVED] Sending Multiple Emails Drawback
Hi,
I have this ActionResult called SendEmails, what it does are as follows:
1. Read data in my Entity Model.
2. Send Email with attachment to the result data of #1.
3. Read data in my Entity Model and stored in a List<Model>
4. Insert data in database by looping through the of List<Model> and add the result data of #3 in New Mail Address.
5. Send Email to all result data in #4.
This is using Synchronous Action and working fine, however, the drawback is that on the screen of the user the pre-loader takes times (due to it takes time to finish all the process).
Is there a way to finish all the process in the background, so that the user can navigate the application without waiting to pre-loader to end. Just like Google Gmail, after you send the email, you can navigate and do something else, while it will give you a notification on the top of the screen, telling if the email successfully send or not.
I read about Asynchronous Action but I think it's not the thing that will do base on what I want to do.
Help Appreciated.
Thank you
-
May 18th, 2018, 11:18 PM
#2
Re: Sending Multiple Emails Drawback
Well there is only Synch or Asynch... there are no other options. Synchronous calls wait to be completed before continuing... Asynchronous means you call it and it returns right away even as it does it's own thing... which sounds like exactly what you DO need.
-tg
-
May 19th, 2018, 12:11 AM
#3
Re: Sending Multiple Emails Drawback
Performing operations that are asynchronous from the perspective of the user in web apps can be a pain. There's obviously more to it than just using multiple threads. We've recently deployed an ASP.NET Core (.NET Framework) application in my office where we use Exchange to send meeting invitations and that can take some time. The user clicks a button to initiate the operation and then they see a progress bar fill as invitations sent. The code is C# but the principle is the same in VB. Here's some of the relevant code, i.e. script and controller actions:
javascript Code:
function startSendInvitations() { if ($("#sendInvitationsButtonDiv") != null) { SI.Dpc.EventsHub.disableButtons(["#saveButtonDiv", "#sendInvitationsButtonDiv"]); $("#sendInvitationsButton").html("Sending Invitations..."); SI.DataTransfer.jsonPostToJson("@Url.Content($"~/meetings/sendmeetinginvitationsstart/{Model.MeetingId}")", @Model.MeetingId, runSendInvitations); } }
csharp Code:
[HttpPost] public async Task<IActionResult> SendMeetingInvitationsStart(long id) { //this action (plus SendMeetingInvitationsRun and SendMeetingInvitationsProgress) need to cater for: //If user calls this action more than once very quickly eg via a double click //If two or more users call this action at the same time for the same meeting var currentUserId = GetCurrentUserId(); var meetingDto = meetingService.GetMeeting(id); var meetingInvitationSectionAccessLevels = await GetCurrentUserMeetingTypeSectionAccessLevelAsync(meetingDto.MeetingTypeId, MeetingTypeSection.Invitations, true); var longRunningProcessReturnModel = new LongRunningProcessReturnModel(); if (meetingInvitationSectionAccessLevels >= AccessLevel.Edit) { LongRunningProcessDto longRunningProcessDto = null; //check if invitations are currently being sent var longRunningProcessDtos = await longRunningProcessService.GetLongRunningProcessByTypeAndOwnerIdAsync(LONG_RUNNING_PROCESS_TYPE_MEETING_SEND_INVITATIONS, id); if (longRunningProcessDtos.Count == 1) { //use first existing long running process longRunningProcessDto = longRunningProcessDtos[0]; } else if (longRunningProcessDtos.Count > 1) { //use first existing long running process longRunningProcessDto = FindFirstLongRunningProcessAndDeleteOthers(longRunningProcessDtos); } //check if we need to create a new long running process if (longRunningProcessDto == null) { //add new long running process record to database longRunningProcessDto = new LongRunningProcessDto(); longRunningProcessDto.Progress = 0; longRunningProcessDto.OwnerId = id; longRunningProcessDto.Type = LONG_RUNNING_PROCESS_TYPE_MEETING_SEND_INVITATIONS; await longRunningProcessService.SaveLongRunningProcessAsync(longRunningProcessDto); } longRunningProcessReturnModel = Mapper.Map<LongRunningProcessDto, LongRunningProcessReturnModel>(longRunningProcessDto); longRunningProcessReturnModel.IsSuccessful = true; } LogLongRunningProcessReturnModel(longRunningProcessReturnModel, "Meetings", "SendMeetingInvitationsStart"); return Json(longRunningProcessReturnModel); }
javascript Code:
function runSendInvitations(data, textStatus, jqXHR) { if ($("#sendInvitationsButtonDiv") != null) { if (data.isSuccessful) { //update progress updateSendInvitationsProgress(data.progress); SI.DataTransfer.jsonPostToJson("@Url.Content($"~/meetings/sendmeetinginvitationsrun/{Model.MeetingId}")", @Model.MeetingId, displaySendInvitationsProgress); } else { afterSendInvitations(data, textStatus, jqXHR); } } }
csharp Code:
[HttpPost] public async Task<IActionResult> SendMeetingInvitationsRun([FromBody] long id) { var longRunningProcessReturnModel = new LongRunningProcessReturnModel(); var currentUserId = GetCurrentUserId(); var meetingDto = meetingService.GetMeeting(id); var meetingInvitationSectionAccessLevels = await GetCurrentUserMeetingTypeSectionAccessLevelAsync(meetingDto.MeetingTypeId, MeetingTypeSection.Invitations, true); if (meetingInvitationSectionAccessLevels >= AccessLevel.Edit) { LongRunningProcessDto longRunningProcessDto = null; //check if invitations are currently being sent var longRunningProcessDtos = await longRunningProcessService.GetLongRunningProcessByTypeAndOwnerIdAsync(LONG_RUNNING_PROCESS_TYPE_MEETING_SEND_INVITATIONS, id); if (longRunningProcessDtos.Count == 1) { //use first existing long running process longRunningProcessDto = longRunningProcessDtos[0]; } else if (longRunningProcessDtos.Count > 1) { //use first existing long running process longRunningProcessDto = FindFirstLongRunningProcessAndDeleteOthers(longRunningProcessDtos); } //check if process has started if (longRunningProcessDto != null && !longRunningProcessDto.StartedAt.HasValue) { longRunningProcessDto.StartedAt = DateTime.Now; await longRunningProcessService.SaveLongRunningProcessAsync(longRunningProcessDto); longRunningProcessReturnModel = Mapper.Map<LongRunningProcessDto, LongRunningProcessReturnModel>(longRunningProcessDto); var baseUrl = Url.GetBaseUrl(); //we do not want to wait for this task to complete before returning var task = Task.Run(async () => { try { //set up progress callback var progress = new Progress<int>(async percent => { //record progrees in long running process longRunningProcessDto.Progress = percent; await longRunningProcessService.SaveLongRunningProcessAsync(longRunningProcessDto); }); await invitationService.AddAndSendMeetingInvitationsAsync(id, currentUserId, baseUrl, progress); //to be neat save with 100% first longRunningProcessDto.Progress = 100; await longRunningProcessService.SaveLongRunningProcessAsync(longRunningProcessDto); //delete long running process await longRunningProcessService.DeleteLongRunningProcessAsync(longRunningProcessDto.LongRunningProcessId); } catch (Exception ex) { logger.Log(NLog.LogLevel.Error, ex); } }).ConfigureAwait(false); longRunningProcessReturnModel.IsSuccessful = true; } } LogLongRunningProcessReturnModel(longRunningProcessReturnModel, "Meetings", "SendMeetingInvitationsRun"); return Json(longRunningProcessReturnModel); }
javascript Code:
function displaySendInvitationsProgress(data, textStatus, jqXHR) { //update progress updateSendInvitationsProgress(data.progress); if (!data.hasProcessCompleted) { //get progress again setTimeout(function() { SI.DataTransfer.jsonPostToJson('@Url.Content("~/meetings/sendmeetinginvitationsprogress")', @Model.MeetingId, displaySendInvitationsProgress); }, 2000); } else { afterSendInvitations(data, textStatus, jqXHR); } } function updateSendInvitationsProgress(progress) { if (progress != null) { if ($("#sendInvitationsButtonDiv").hasClass('ButtonDisabled')) { //update progress text $("#sendInvitationsButton").html("Sending Invitations..." + progress + "%"); } } }
csharp Code:
[HttpPost] public async Task<IActionResult> SendMeetingInvitationsProgress([FromBody] long id) { LongRunningProcessReturnModel longRunningProcessReturnModel = null; //get any long running processes var longRunningProcessDtos = await longRunningProcessService.GetLongRunningProcessByTypeAndOwnerIdAsync(LONG_RUNNING_PROCESS_TYPE_MEETING_SEND_INVITATIONS, id); //check if any have been running for too long foreach (var longRunningProcessDtoToCheck in longRunningProcessDtos) { if (longRunningProcessDtoToCheck.StartedAt.HasValue && longRunningProcessDtoToCheck.StartedAt.Value.AddMinutes(LONG_RUNNING_PROCESS_TYPE_MEETING_SEND_INVITATION_MAX_RUN_TIME_IN_MINUTES) < DateTime.Now) { //delete long running process await longRunningProcessService.DeleteLongRunningProcessAsync(longRunningProcessDtoToCheck.LongRunningProcessId); //clear start date for use below longRunningProcessDtoToCheck.StartedAt = null; } } LongRunningProcessDto longRunningProcessDto = null; if (longRunningProcessDtos.Count == 1) { //use first existing long running process longRunningProcessDto = longRunningProcessDtos[0]; } else if (longRunningProcessDtos.Count > 1) { //use first existing long running process longRunningProcessDto = FindFirstLongRunningProcessAndDeleteOthers(longRunningProcessDtos); } if (longRunningProcessDto != null) { longRunningProcessReturnModel = Mapper.Map<LongRunningProcessDto, LongRunningProcessReturnModel>(longRunningProcessDto); } else { //assume long running service has already been completed and deleted longRunningProcessReturnModel = new Models.LongRunningProcessReturnModel(); longRunningProcessReturnModel.HasProcessCompleted = true; //check if was successful var meetingDto = meetingService.GetMeeting(id); if (!string.IsNullOrWhiteSpace(meetingDto?.ExchangeAppointmentId)) { longRunningProcessReturnModel.WasProcessSuccessful = true; } } LogLongRunningProcessReturnModel(longRunningProcessReturnModel, "Meetings", "SendMeetingInvitationsProgress"); return Json(longRunningProcessReturnModel); }
javascript Code:
function afterSendInvitations(data, textStatus, jqXHR) { if (data.wasProcessSuccessful) { SI.UI.showResult("sendInvitationsButton", "Invitations Sent", "Successful", "sendInvitationsButtonDiv", function () { if ($("#sendInvitationsButtonDiv") != null) { $("#sendInvitationsButton").html("Send Invitations"); $("#sendInvitationsButtonDiv").hide(); } $("#syncInvitationResponses").removeClass('Disabled'); $("#resendNoResponseInvitations").removeClass('Disabled'); $("#deleteInvitations").removeClass('Disabled'); }); } else { SI.UI.showResult("sendInvitationsButton", "Send Invitations Failed", "Failed", "sendInvitationsButtonDiv", function () { $("#sendInvitationsButton").html("Send Invitations"); }); } SI.Dpc.EventsHub.enableButtons(["#saveButtonDiv", "#sendInvitationsButtonDiv"]); reloadInvitationsIfLoaded(); }
-
May 20th, 2018, 08:20 PM
#4
Thread Starter
Lively Member
Re: Sending Multiple Emails Drawback
@jm - that suffice the work that I need.
Thank you for the replies.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|