Update UI using partial view in response of Task Run (Async) - c#

I am working on MVC C# Razor Framework 4.6.
I have static method ExportManager.ExportExcelCannedReportPPR wrapped up in Task.Run for long running report. This method returns boolean value and based on that I am refreshing partial view (_NotificationPanel).
public ActionResult ExportCannedReport(string cannedReportKey, string cannedReportName)
{
string memberKeys = _curUser.SecurityInfo.AccessibleFacilities_MemberKeys; //ToDo: Make sure this is fine or need to pass just self member?
string memberIds = _curUser.SecurityInfo.AccessibleFacilities_MemberIDs; //ToDo: Make sure this is fine or need to pass just self member?
string curMemberNameFormatted = _curUser.FacilityInfo.FacilityName.Replace(" ", string.Empty);
string cannedReportNameFormatted = cannedReportName.Replace(" ", string.Empty);
string fileName = string.Concat(cannedReportNameFormatted, "_", DateTime.Now.ToString("yyyyMMdd"), "_", curMemberNameFormatted);
//ToDo: Make sure below getting userId is correct
string userId = ((_curUser.IsECRIStaff.HasValue && _curUser.IsECRIStaff.Value) ? _curUser.MembersiteUsername : _curUser.PGUserName);
var returnTask = Task.Run<bool>(() => ExportManager.ExportExcelCannedReportPPR(cannedReportKey, cannedReportName, fileName, memberIds, userId));
returnTask.ContinueWith((antecedent) =>
{
if (antecedent.Result == true)
{
return PartialView("_NotificationPanel", "New file(s) added in 'Download Manager'.");
}
else
{
return PartialView("_NotificationPanel", "An error occurred while generating the report.");
}
}, TaskContinuationOptions.OnlyOnRanToCompletion);
return PartialView("_NotificationPanel", "");
}
Now issue is that UI could not get refresh even though _NotificationPanel in ContinueWith get executed.

The issue is that once you return from it - that request is done. You cannot return from it multiple times for a single request. The request and response are a 1-to-1. You need to use async and await here, such that when the export is done then and only then return a result.
public async Task<ActionResult> ExportCannedReport(string cannedReportKey,
string cannedReportName)
{
// Omitted for brevity...
var result =
await Task.Run<bool>(() =>
ExportManager.ExportExcelCannedReportPPR(cannedReportKey,
cannedReportName,
fileName,
memberIds,
userId));
return PartialView("_NotificationPanel",
result
? "New file(s) added in 'Download Manager'."
: "An error occurred while generating the report.");
}
You need to make the method Task returning such that it is "awaitable". Then you mark the method as async which enables the await keyword. Finally, you're ready to execute the long running task and from the result correctly determine and return the desired partial view update.
Update
Alternatively, you could utilize an AJAX call on the client and update once the server responds. For details on that specifically checkout MSDN.

Related

MS Graph API: Copying a driveItem using PostAsync does not return a result

I want to copy a file from one folder in a Sharepoint document library to another folder in the library.
I'm using
Task<DriveItem> copyDriveItemTask = _graphClient.Sites.Root.Lists[_nameOfDocLib].Drive.Root.ItemWithPath(_pathToItemLocation)
.Copy(newName, parentReference).Request().PostAsync();
copyDriveItemTask.Wait();
After the copyDriveItemTask returns, its result is null!
Trying to access the new file does not work every time. I think copying the file has not been finished at this time. If I walkthrough step by step in debug mode it works all the time, giving the Sharepoint server enough time to complete his copy actions.
According to Graph.Api's http documentation the PostAsync call should return with a response containing a link to retrieve the progress of the copy process.
In the c# documentation such a result is missing. The PostAsync here is called with simple await!
According to the Graph.Api's IDriveItemCopyRequest interface PostAsync should return a Task<DriveItem>.
I think the c# PostAsync call does not wait until the file is entirely copied. It returns immediately as it is described for the http variant (missing the link to retrieve the progress). But at this time there is no DriveItem that can be returned. So it ends up with a result null.
Whether my explanation is correct or not, in the c# variant I have no chance to determine, if the copy process has terminated or not.
Better explanations, other help or even a solution are appreciated ...
Kalle
public class GraphMonitorResponseModel
{
public float PercentageComplete { get; set; }
public string ResourceId { get; set; }
public string Status { get; set; }
}
var res = await _graphClient.Sites.Root.Lists[_nameOfDocLib]
.Drive.Root.ItemWithPath(_pathToItemLocation)
.Copy(newName, parentReference).Request().PostAsync();
var resMon = await getStatus();
while (resMon.Status != "completed")
{
if (resMon.Status == "failed") //do what you need to and return.
if (resMon.Status == "in-progress") //do what you need to.
await Task.Delay(100);
resMon = await getStatus();
}
//Copy will be complete or failed if the code reaches this point.
async Task<GraphMonitorResponseModel> getStatus()
=> JsonConvert.DeserializeObject<GraphMonitorResponseModel>
(await _http.GetStringAsync(res.Headers.Location));
Ref:
https://learn.microsoft.com/en-us/graph/api/driveitem-copy?view=graph-rest-1.0&tabs=http
https://learn.microsoft.com/en-us/graph/api/resources/asyncjobstatus?view=graph-rest-beta&viewFallbackFrom=graph-rest-1.0
To see result of postAsync method in debug execute 2 steps. Screenshot: PostAsyn
return HttpResponseMessage from method with postAsync:
private static async Task<HttpResponseMessage> methodWithPostAsync(){
...
response = await client.PostAsync(url, data);
return response
}
call method and wait for response message status:
Task<HttpResponseMessage> content= methodWithPostAsync();
while (!content.IsCompleted)
{
Console.WriteLine("busy");
System.Threading.Thread.Sleep(1000);
}

500 server error due to absence of async in controller? Need help removing an object from a list C#

I am trying to filter out all errors except one from a list of errors I send back to the front end. I realize this operation should be an async operation as my request is giving a 500 internal server error. I am new to C# and am having a hard time figuring out how to do so.
My code that gets invoked on the route request looks like:
public async Task<ActionResult> Index(ProfileParams profileParameters)
{
// ...... //
var user = await GenerateUser(Request.RequestContext);
var userState = await _userStateFactory.CreateAsync(user);
var stateTree = new BusinessProfileStateTreeModel
{
Global = await _globalStateFactory.CreateAsync(user),
Header = await _headerStateFactory.CreateAsync(user, null),
User = userState,
Modals = _modalsStateFactory.Create(),
Page = CreatePageState(),
BusinessProfile = _businessProfileReviewsStateFactory.Create(viewModel, customerReviewModel),
Analytics = await _analyticsStateFactory.CreateAsync(user, CreateDtmData(viewModel?.Categories?.PrimaryCategoryName, profileBbbInfo?.BbbName, viewModel), userState)
};
// trying to filter out errors here from the state tree alerts
var errors = filterErrorsAsync(stateTree.BusinessProfile.Display.Alerts.AllAlerts);
var metaData =
GenerateProfileMetaData(customerReviewModel.NumFound, viewModel.ProfileUrl.ToUrlString(), viewModel);
var serverSideModel =
GenerateServerSideModel(
viewModel,
metaData,
profileBbbInfo,
stateTree.Analytics.DtmData,
user);
return await ReduxViewAsync(stateTree.ToList(), serverSideModel);
}
}
The filterErrorsAsync method looks like:
private List<BPAlert> filterErrorsAsync(List<BPAlert> allAlerts)
{
foreach (BPAlert alert in allAlerts)
{
if (alert.AlertTypeId == (int)BusinessReportCustomTextType.CustomerReviews)
{
allAlerts.Clear();
allAlerts.Add(alert);
}
}
return allAlerts;
}
Can someone tell me how to achieve this correctly?
You can't loop a list and modify it at the same time. This is probably what is causing your 500 error.
It looks like you only want filter out certain errors from a list. If you want to keep your method as a loop you can do:
private List<BPAlert> filterErrorsAsync(List<BPAlert> allAlerts)
{
List<BPAlert> temp = new List<BPAlert>(); //copy into new list
foreach (BPAlert alert in allAlerts)
{
if (alert.AlertTypeId == (int)BusinessReportCustomTextType.CustomerReviews)
{
temp.Add(alert);
}
}
return temp;
}
If you want to be a little more modern you can also just use LINQ
private List<BPAlert> filterErrorsAsync(List<BPAlert> allAlerts)
{
return allAlerts.Where(alert => alert.AlertTypeId == (int)BusinessReportCustomTextType.CustomerReviews).ToList();
}
You're attempting to modify a list while enumerating it which won't work. Since you already know which kind of error you want to filter to, you can utilize LINQ's Where method to filter out the other errors, then use Take to get the first one.
private List<BPAlert> filterErrors(List<BPAlert> allAlerts)
=> allAlerts.Where(alert => alert.AlertTypeID == (int)BusinessReportCustomTextType.CustomerReviews)
.Take(1)
.ToList();
There isn't anything asynchronous happening in this method, so no need to mark it async.

Microsoft Graph throws Request_ResourceNotFound instead of null/0

I'm an apprentice with 4 months of experience and I got a task to build a holiday request application using data from Microsoft Graph. One of the functions of app is to look up a user'ss manager and display it on the dashboard. Everything was going smooth until my boss logged in. After running Microsoft Graph Query To find out current user Manager, Graph Api returns and error(Request_ResourceNotFound) and breaks whole application instead of returning null or 0. I don't know how to handle that error.
I have tried to return null if the result is null, but that didn't do anything.
This what my controller expects:
var allUsersConnectedToCurrentManagerDisplayName = graphHelper.GetManagerForCurrentUser(userIdToCheck).DisplayName;
var allUsersConnectedToCurrentManagerEmail = graphHelper.GetManagerForCurrentUser(userIdToCheck).UserPrincipalName;
var allUsersConnectedToCurrentManagerId = graphHelper.GetManagerForCurrentUser(userIdToCheck).Id;
Microsoft Graph Helper:
User GetDirectManagerForUser(GraphServiceClient _graphServiceClient, string managerId)
{
using(var task = Task.Run(async() => await _graphServiceClient.Users[managerId].Manager.Request().GetAsync()))
{
while (!task.IsCompleted)
Thread.Sleep(200);
var manager = task.Result as Microsoft.Graph.User;
return manager;
}
}
I was expecting this to return null and just don't display a direct manager for the user without anyone above him.
So you've got a few things going on here.
The first, and the most glaring, issue is that your code is requesting the same User record from Graph three times in a row. Each call you're making to GetDirectManagerForUser is downloading the entire User profile. You want to avoid doing this:
var manager = await graphHelper.GetManagerForCurrentUser(userIdToCheck);
var allUsersConnectedToCurrentManagerDisplayName = manager.DisplayName;
var allUsersConnectedToCurrentManagerEmail = manager.UserPrincipalName;
var allUsersConnectedToCurrentManagerId = manager.Id;
The second issue to avoid is wrapping your request in a Task like that. It adds a lot of complexity to the code, makes it super hard to debug, and isn't necessary. Simply add async Task<> at the method level and let the compiler handle wiring it up for you:
async Task<User> GetDirectManagerForUser(GraphServiceClient _graphServiceClient, string managerId)
Third, your casting the result but not capturing any exceptions (i.e. the 404 your getting). You want to capture these and return an empty User:
var manager = await graphHelper.GetManagerForCurrentUser(userIdToCheck);
var allUsersConnectedToCurrentManagerDisplayName = manager.DisplayName;
var allUsersConnectedToCurrentManagerEmail = manager.UserPrincipalName;
var allUsersConnectedToCurrentManagerId = manager.Id;
async Task<User> GetDirectManagerForUser(GraphServiceClient _graphServiceClient, string managerId)
{
try
{
// Normal path
return await _graphServiceClient
.Users[managerId]
.Manager
.Request()
.GetAsync();
}
catch (Exception)
{
// Something went wrong or no manager exists
var emptyUser = new User();
}
}
You have to catch the exception in order to return null.
I would write the function like this:
public User GetDirectManagerForUser(GraphServiceClient _graphServiceClient, string managerId)
{
//.Result, because this function in synchronious
try
{
var manager = await _graphServiceClient.Users[managerId].Manager.Request().GetAsync().Result;
return manager;
}
catch(Exception)
{
return null;
}
}
You could also make the function async like this:
public async Task<User> GetDirectManagerForUser(GraphServiceClient _graphServiceClient, string managerId)
{
try
{
var manager = await _graphServiceClient.Users[managerId].Manager.Request().GetAsync();
return manager;
}
catch(Exception)
{
return null;
}
}
Why haven't you specified an accessibility level?

Call an asynchronous method in a non-asynchronous asp.net page

I am facing an issue with two different endpoints in my single asp.net app. Basically, the issue is that one of the endpoints does not allow asynchronous methods in the page and the other endpoint does. If I run the app one endpoint will ask me to have an asynchronous asp.net page, but the other one crashes and vice versa.
public async Task<AirtableListRecordsResponse> RetrieveRecord()
{
string MyProductID = ProductID;
string baseId = "00000000000xxxx";
string appKey = "00000000000xxxx";
var records = new List<AirtableRecord>();
using (AirtableBase airtableBase = new AirtableBase(appKey, baseId))
{
Task<AirtableListRecordsResponse> task = airtableBase.ListRecords(tableName: "efls", filterByFormula: ProductID);
AirtableListRecordsResponse response = await task;
if (!response.Success)
{
string errorMessage = null;
if (response.AirtableApiError is AirtableApiException)
{
errorMessage = response.AirtableApiError.ErrorMessage;
}
else
{
errorMessage = "Unknown error";
}
// Report errorMessage
}
else
{
records.AddRange(response.Records.ToList());
var record = response.Records;
//offset = response.Offset;
//var record = response.Record;
foreach (var item in record)
{
foreach (var Fields in item.Fields)
{
if (Fields.Key == "pdfUrl")
{
string link = Fields.Value.ToString();
MyLink = Fields.Value.ToString();
}
}
}
// Do something with your retrieved record.
// Such as getting the attachmentList of the record if you
// know the Attachment field name
//var attachmentList = response.Record.GetAttachmentField(YOUR_ATTACHMENT_FIELD_NAME);
}
return response;
}
}
This is the asynchronous method which asks for an asynchronous page, the other contains a strong structure and it cannot be changed for any reason. Is there any way to make them work together?
I am using airtable.com api by the way.
Thanks in advance.
I solved by my own,
The solution I found is the following:
When a page works with two different endpoints and one of them obligates the page to be asynchronous the best solution is to split the procedures into two different sections and/or pages, one of them will call the asynchronous methods and retrieves the info and other works without being asynchronous.
How can I pass the information between the sites?
Using session variables, there are endpoints which only needs to display simple data as in this case, so the session variables will be called in the page #2 which is the non-asynchronous page.
It is a simple solution but effective.
Thank you very much to all for you answers.
Using Wait on Task, you can use synchronous method
Task<AirtableListRecordsResponse> task = Task.Run(() => airtableBase.ListRecords(tableName: "efls", filterByFormula: ProductID));
task.Wait();
AirtableListRecordsResponse response = task.Result;
Use it only when you cannot use async method.
This method is completely deadlock free as mentioned on msdn blog-
https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-in-main-context/

How to check in MongoDB C# Driver if updated successfully?

I am using the following code to update data using the MongoDB C# Driver:
public async Task<bool> UpdateFirstName(string id, string firstName)
{
await Collection.UpdateOneAsync(Builders<User>.Filter.Eq(
"_id", new ObjectId(id)),
Builders<User>.Update.Set("firstName", firstName)
.CurrentDate("lastUpdatedDate"));
}
This method returns "bool", because I want to know if the data has been updated successfully. This would be the pseudocode for checking if the data has been updated successfully:
if (data updated successfully)
{
return true;
}
else
{
return false;
}
Does anyone know how to write the code for checking if the data updated successfully? Thanks!
If the method was executed so the update was done, otherwise the method will throw an exception - In case of async it's important to not forget the await (since using async method without await can't ensure your application stay around long enough to retreive the exception)
UpdateOneAsync has a return value of UpdateResult?, which gives you access to the ModifiedCount. As you you UpdateOne a single greater 0 check is enough.
var response = await Collection.UpdateOneAsync(Builders<User>.Filter.Eq(
"_id", new ObjectId(id)),
Builders<User>.Update.Set("firstName", firstName)
.CurrentDate("lastUpdatedDate"));
if response.ModifiedCount > 0 {
// success
}
// failed
This will throw an Exception if the Update is not acknowledged.

Categories

Resources