UserContext becomes null after async call - c#

I have a web service call in my function Verify.
The code is as follows:
public async Task<ActionResult> Index()
{
....
UserContext.LoginUser = null;
if (!string.IsNullOrEmpty(etoken))
{
SSOLogin ssoLogin = new SSOLogin();
LoginUser user = await ssoLogin.Verify(etoken);
UserContext.LoginUser = user;
if (UserContext.LoginUser == null)
return RedirectToAction("UnAuthorized", "Login");
else
{
Session["authenticated"] = true;
userId = UserContext.LoginUser.UserId;
domain = UserContext.LoginUser.Domain;
}
}
}
public async Task<LoginUser> Verify(string etoken)
{
string publicKey = "xxx";
LoginUser loginUser = null;
WSVerifyAccessToken wsVerifyAccessToken = new WSVerifyAccessToken();
string verifyResponse = await wsVerifyAccessToken.VerifyAccessToken(etoken); // Inside, this is a web service call
if (!string.IsNullOrEmpty(verifyResponse))
{
/* Some processing here */
}
return loginUser;
}
The problem is that, the UserContext becomes null after the Verify function
LoginUser user = await ssoLogin.Verify(etoken);
Hence when I assign
UserContext.LoginUser = user;
gives me the error System.NullReferenceException: Object reference not set to an instance of an object..
I tried to make the function to be synchronous
LoginUser user = await ssoLogin.Verify(etoken).Result;
but then the web service call will just hang there and never finishes.
Any solution?

This is a known issue when the runtime target framework is 4.0, and not 4.5 or above, in the web.config file. This can differ from the target framework your project is set up for. Make sure that the runtime target framework is 4.5 or higher.
Additionally:
The UserContext is bound to the Thread initializing the Request. Therefore, you should return the callback of your awaited call to the original context. Even though the ConfigureAwait(true) is the default value, you should ensure the correct context is used in the callback by specifying the following:
UserContext.LoginUser = await ssoLogin.Verify(etoken).ConfigureAwait(true);
See a more detailed description regarding the configure await method here

Related

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?

HttpClient.GetAsync immediately throws TaskCanceledException

I had a working code that was returning something from my CommonRestClient(which is simple wrapper for HttpClient):
public async Task<IActionResult> Index()
{
var uri = _baseUri.Concat(AvalancheServiceAdresses.NodeService);
using (var client = new CommonRestClient(uri))
{
var result = await client.GetAsync<List<NodeInfoContract>>(new Uri(NodeServiceConstant.NodesContractUrl, UriKind.Relative), null);
return View(result);
}
}
It worked fine and i was happy. Until I decide to move my code from view in another class, which should incapsulate entire REST logic and provide an OOP API.
So now my index looks like:
public async Task<IActionResult> Index()
{
var nodeInfoContracts = await _configManager.GetNodesList();
return View(nodeInfoContracts);
}
where GetNodesList is
public ConfiguredTaskAwaitable<List<NodeInfoContract>> GetNodesList()
{
var uri = _baseUri.Concat(AvalancheServiceAdresses.NodeService);
using (var client = new CommonRestClient(uri))
{
return client.GetAsync<List<NodeInfoContract>>(new Uri(NodeServiceConstant.NodesContractUrl, UriKind.Relative), null);
}
}
It's clear that provided codes are equal.
But now it always throws an exception when I try to get a result. In my GetAsync method it fails on following line with TaskCanceledException:
var response = await _client.GetAsync(url).ConfigureAwait(false);
But it's interestring: when I place a breakpoint on this line and step over its works fine. So here we have race condition or similar.
Why am I getting it? I tried to place CondigureAwait false/true, combining some code, but it always throws an error when breakpoints are off. I checked timeout which is several minutes, it can't cause this error.
In the second code snippet the client is disposed before the IO completed. Use the first form.

Correct pattern to call a service containing an async call from an MVC controller

I am new to TAP and async/await in practice in C# so I may have some bad code smells here, so be gentle. :-)
I have a service method that looks as follows:
public OzCpAddOrUpdateEmailAddressToListOutput AddOrUpdateEmailAddressToList(
OzCpAddOrUpdateEmailAddressToListInput aParams)
{
var result = new OzCpAddOrUpdateEmailAddressToListOutput();
try
{
var mailChimManager = new MailChimpManager(aParams.MailChimpApiKey);
Task<Member> mailChimpResult =
mailChimManager.Members.AddOrUpdateAsync(
aParams.Listid,
new Member
{
EmailAddress = aParams.EmailAddress
});
//Poll async task until it completes.
//Give it at most 8 seconds to do what it needs to do
var outOfTime = DateTime.Now.AddSeconds(8);
while (!mailChimpResult.IsCompleted)
{
if (DateTime.Now > outOfTime)
{
throw new Exception("Timed out waiting for MailChimp API.");
}
}
//Should there have been a problem with the call then we raise an exception
if (mailChimpResult.IsFaulted)
{
throw new Exception(
mailChimpResult.Exception?.Message ??
"Unknown mail chimp library error.",
mailChimpResult.Exception);
}
else
{
//Call to api returned without failing but unless we have
//the email address subscribed we have an issue
if (mailChimpResult.Result.Status != Status.Subscribed)
{
throw new Exception(
$"There was a problem subscribing the email address
{aParams.EmailAddress} to the mailchimp list id
{aParams.Listid}");
}
}
}
catch (Exception ex)
{
result.ResultErrors.AddFatalError(PlatformErrors.UNKNOWN, ex.Message);
}
return result;
}
But when I call in from MVC Controller action mailChimpResult.IsCompleted always returns false and eventually I hit the timeout.
I realise this is because I am not chaining the async calls as per HttpClient IsComplete always return false and because of different threads this behaviour is "expected".
However I want my service method to hide the complexity of the async nature of what it is doing and merely do what appears to be a synchronous call in my action method namely:
var mailChimpResult =
_PlatformMailChimpService.AddOrUpdateEmailAddressToList(
new OzCpAddOrUpdateEmailAddressToListInput
{
EmailAddress = aFormCollection["aEmailAddress"],
Listid = ApplicationSettings.Newsletter.MailChimpListId.Value,
MailChimpApiKey = ApplicationSettings.Newsletter.MailChimpApiKey.Value
});
if (mailChimpResult.Result == true)
{
//So something
}
Ideally you should avoid the .Result and .IsFaulted properties of the Task and Task<T> objects, that was code smell number one. When you're using these objects you should use async and await through the entire stack. Consider your service written this way instead:
public async Task<OzCpAddOrUpdateEmailAddressToListOutput>
AddOrUpdateEmailAddressToList(
OzCpAddOrUpdateEmailAddressToListInput aParams)
{
var result = new OzCpAddOrUpdateEmailAddressToListOutput();
try
{
var mailChimManager = new MailChimpManager(aParams.MailChimpApiKey);
Member mailChimpResult =
await mailChimManager.Members.AddOrUpdateAsync(
aParams.Listid,
new Member
{
EmailAddress = aParams.EmailAddress
});
}
catch (Exception ex)
{
result.ResultErrors.AddFatalError(PlatformErrors.UNKNOWN, ex.Message);
}
return result;
}
Notice that I was able to remove all of that unnecessary polling and examining of properties. We mark the method as Task<OzCpAddOrUpdateEmailAddressToListOutput> returning and decorate it with the async keyword. This allows us to use the await keyword in the method body. We await the .AddOrUpdateAsync which yields the Member.
The consuming call into the service looks similar, following the same paradigm of async and await keywords with Task or Task<T> return types:
var mailChimpResult =
await _PlatformMailChimpService.AddOrUpdateEmailAddressToList(
new OzCpAddOrUpdateEmailAddressToListInput
{
EmailAddress = aFormCollection["aEmailAddress"],
Listid = ApplicationSettings.Newsletter.MailChimpListId.Value,
MailChimpApiKey = ApplicationSettings.Newsletter.MailChimpApiKey.Value
});
if (mailChimpResult.Result == true)
{
//So something
}
It is considered best practice to postfix the "Async" word to the method, to signify that it is asynchronous, i.e.; AddOrUpdateEmailAddressToListAsync.

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

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.

Unable to await method in ASP.NET (4.6) MVC5 project

I'm currently seeing a problem whereby my await method is just hanging, and causing the response to just hang, not doing anything until I kill the request. This is evident in both Chrome debug tools and Fiddler.
I have the following API action defined:
[Route("state/{stateCode}")]
[LogApiCallFilter]
public async Task<IList<MapPlaceDTO>> GetWithinState(string stateCode)
{
//
// Additional code truncated for SO
// Via debugging I know that the 'state' variable below is correct
//
IList<Place> places = await _placeManager.GetPlacesInState(state);
// Instantiate the list of places.
IList<MapPlaceDTO> mapPlaces = new List<MapPlaceDTO>();
// Iterate through the places and add to the map place list
foreach (Place place in places)
{
mapPlaces.Add(MapPlaceDTO.FromPlace(place));
}
return mapPlaces;
}
When I step through that code in debug mode for a unit test for the GetWithinState action, the IList<Place> places = await _placeManager.GetPlacesInState(state); method runs without exception, however I am not able to hover over the places variable to inspect it, nothing happens. Nor can I add it to the watch list, I get the following message:
error CS0103: The name 'places' does not exist in the current context
Interestingly however, if I run the exact same code within a "PlaceManager" unit test, outside of the Web API project, the test runs fine, and I can inspect the places variable.
[Fact(DisplayName = "Can_Get_All_Places_Within_State")]
[Trait("Category", "Place Manager")]
public async Task Can_Get_All_Places_Within_State()
{
State state = new State()
{
ShortName = "VIC",
Name = "Victora",
CountryCode = "AU"
};
IList<Place> places = await _placeManager.GetPlacesInState(state);
Assert.NotNull(places);
Assert.True(places.Count > 0);
}
This is the code that runs within the PlaceManager.GetPlacesInState method:
public async Task<IList<Place>> GetPlacesInState(State state)
{
if (state == null)
{
throw new ArgumentNullException("state", "The 'state' parameter cannot be null.");
}
// Build the cache key
string cacheKey = String.Format("places_state_{0}", state.Id);
// Get the places from the cache (if they exist)
IList<Place> places = CacheManager.GetItem<IList<Place>>(cacheKey);
// Get the places from the database.
if (places == null)
{
// Get the places from the database
places = await _repository.Find(i => i.State.ToLower() == state.ShortName.ToLower() && i.Country.ToLower() == state.CountryCode.ToLower());
// If there are places, then add to the cache for next time
if (places != null && places.Count > 0)
{
CacheManager.AddItem(cacheKey, places);
}
}
// return the places
return (places != null ? places : new List<Place>());
}
Does anyone have any idea why this may be occurring within the API method, but is working fine in unit tests?
As mentioned by Alexei Levenkov above, I was causing a deadlock using the getDataAsync().Result code.
While mapping my Place object to a MapPlaceDTO object, the Place object has a get property to load the place type, which would call an async function in the following way:
public PlaceType PlaceType
{
get
{
return getPlaceType().Result;
}
}
Once I removed that property and just called the GetPlaceType method directly using the await keyword, everything started working correctly. Thanks Alexei!

Categories

Resources