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?
Related
I have generic Cosmos method to retrieve data from Db and its working fine. Now I want to add Polly to it. I am new to the Polly concept.
So, please help me how to add this FeedIterator in the Polly ExecuteAsync() method. Because this is FeedIterator, I don't get status code directly. I need to check whether it has more results or not. I am not sure whether I need to retry when there are no results or status code is not as expected.
If I read the feed iterator I am loosing the data, so I am not able to re-read it. I've been googling, but no use.
Here is my code, Can someone please help?
public async Task<IEnumerable<T>> GetItemsAsync(QueryDefinition query)
{
try
{
var results = new List<T>();
using (FeedIterator resultsIterator = _container.GetItemQueryStreamIterator(query))
{
while (resultsIterator.HasMoreResults)
{
using (ResponseMessage response = await resultsIterator.ReadNextAsync())
{
response.EnsureSuccessStatusCode();
dynamic streamResponse = FromStream<dynamic>(response.Content);
var rawText = ((JsonElement)streamResponse).GetProperty("Documents").GetRawText();
var responseObj = JsonSerializer.Deserialize<List<T>>(rawText, _jsonSerializerOptions);
if (responseObj != null) results.AddRange(responseObj);
}
}
}
return results;
}
catch (CosmosException ex)
{}
}
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
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.
I'm currently working on a Discord bot to learn how to code one. I thought I had it down, but when I try to use the following command, it does nothing:
[Command("ping")]
public async Task Ping(IUser user)
{
await Context.Channel.SendMessageAsync(user.ToString());
}
It's part of a public class, and if I use any other parameter type (e.g. IChannel, bool, int) it works. It's just this one parameter type. It also doesn't log any errors or exceptions. Any ideas?
[Command("ping")]
public async Task Ping(IUser user)
{
await Context.Channel.SendMessageAsync(user.ToString());
}
Your code id perfect. But think about this, the user is of the type IUser and your conversion to sting makes it vague. Instead try this:
[Command("ping")]
public async Task Ping(SocketGuildUser user)
{
await Context.Channel.SendMessageAsync(user.Username);
}
If you want to ping the user try user.Mention.
Also when I started learning I made a bot as well. Here is the source code. Its very very very basic. It definitely will help.
You could try using this workaround for your bot:
public async Task SampleCommand(string user="", [Remainder]string message="")
{
IUser subject = null;
if (user != "")
{
var guilds = (await Context.Client.GetGuildsAsync(Discord.CacheMode.AllowDownload));
var users = new List<IUser>();
foreach (var g in guilds)
users.AddRange(await g.GetUsersAsync(CacheMode.AllowDownload));
users = users.GroupBy(o => o.Id).Select(o => o.First()).ToList();
var search = users.Where(o => o.Username.ToLower().Contains(user.ToLower()) || Context.Message.MentionedUserIds.Contains(o.Id) || o.ToString().ToLower().Contains(user.ToLower())).ToArray();
if (search.Length == 0)
{
await ReplyAsync("***Error!*** *Couldn't find that user.*");
return;
}
else if (search.Length > 1)
{
await ReplyAsync("***Error!*** *Found more than one matching users.*");
return;
}
subject = search.First();
}
// ...
// execute command
Or you could wrap that in a method for easier access and reusability.
Basically, what it does is it looks for available users that match the given string (in nickname, username or mentions. You could also make it check for IDs if you so desire).
Edit: In my case I'm allowing people to mention anyone who shares the server with the bot, but in your case it might be more benefitial to just use the Context.Guild instead and cancel the command in case of DMs.
I ended up taking Reynevan's advice, and wrote a method for converting a mention into an IUser. Just call CustomUserTypereader.GetUser(mention_parameter, Context.Guild);
using System.Threading.Tasks;
using Discord;
public class CustomUserTypereader
{
public static async Task<IUser> GetUserFromString(string s, IGuild server)
{
if (s.IndexOf('#') == -1 || s.Replace("<", "").Replace(">", "").Length != s.Length - 2)
throw new System.Exception("Not a valid user mention.");
string idStr = s.Replace("<", "").Replace(">", "").Replace("#", "");
try
{
ulong id = ulong.Parse(idStr);
return await server.GetUserAsync(id);
}
catch
{
throw new System.Exception("Could not parse User ID. Are you sure the user is still on the server?");
}
}
}
I am not sure what to use in this scenario.
I have an asp.net web api method that basically does this
Finds points of interests from foursquare near user.
Uses the foursquare locations to do queries in my database to find unique data about point of interest near user.
However since I need to store some of the foursquare information to link to my unique data to that location I decided to store all the information in my database and have my database act as my caching system.
This means anything new point of interest that comes in I have to insert into my database, check if it exists and if so then skip it or if it exists check the last refresh date(foursquare policy states all data must be refreshed after 30 day) and if it out past the refresh date I have to update the data.
I want to slow the user down and have to wait for the above to happen. I want my code to do step 1 and then do what I just mentioned while at the same time doing step 2.
Once step 2 finishes I want to return the data and let the user get on their way. If my caching system is not finished then it should keep going but not bog down the user.
I won't use any of these new results in step 2 as if I am inserting it then I won't have any data on that location at this time.
Not sure if I need to make a thread or use the async/await to achieve this.
Edit
Here is what I am trying to do
public HttpResponseMessage Get()
{
// this will do a foursquare lookup to find all stores near the user
// I want to insert them into my database and link it to my unquie data.
// stores pulled from foursquare will
// a) Be new and not in my database
// b) exist in my database but have been refreshed lately
// c) have not been refreshed in timeframe of foursquare policy
// THIS SHOULD WORK IN THE BACKGROUND
storeService.PointsOfInterestNearUser(80, -130); //As you can see it is
//void. Not sure where to put the async/await stuff
// find this product. Should be happening at the same time as above line.
var product = productService.FindProduct("Noodles");
//This will get returned to the user.
// the new stores taht are being added in StoreNearUser
//won't effect this search as I will have not data on this new store
// if existing store is being refreshed it is possible old
//address might be picked up...
//I can live with that as I doubt the address will change much.
// this should happen after product
var allStores = storeService.FindStoresThatHaveItem(product);
// this should be returned as soon as above line is finished.
//If StoreNearUser is not done, it should keep going but not hold up user.
return allStores;
}
public void StoresNearUser(double latitude, double longitude)
{
// get all categories I can about in foursquare.
//First time from db otherwise cached.
List<StoreCategory> storeCategories = GetStoreCategories();
// do a request and get everything in near the user
//(provided it is also in a category I care about)
var request = CreateFoursquareStoreRequest
(latitude, longitude, storeCategories);
// do the actual call.
var response = client.Execute<VenueSearch>(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
// start going through the results, add or update or skip of entry will happen
AddUpdateStores(storeCategories, response);
}
else
{
ErrorSignal.FromCurrentContext().Raise(response.ErrorException);
}
}
Edit 2
public async Task StoresNearUser(double latitude, double longitude)
{
// get all categories I can about in foursquare. First time from db otherwise cached.
List<StoreCategory> storeCategories = GetStoreCategories();
// do a request and get everything in near the user(provided it is also in a category I care about)
var request = CreateFoursquareStoreRequest(latitude, longitude, storeCategories);
await client.ExecuteAsync<VenueSearch>
( request
, response =>
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
AddUpdateStores(storeCategories, response);
}
else
{
ErrorSignal.FromCurrentContext()
.Raise(response.ErrorException);
}
}
);
}
gives me this error
Cannot await 'RestSharp.RestRequestAsyncHandle'
I also don't get the difference between Task and void. From what I read if you just use Task it means you are sending nothing back of meaning, then why not just use void?
Edit 2
I found this post to show me how to make the wrapper for Restsharp. It is not 100% what I want but that is a separate issue.
public async Task StoresNearUser(double latitude, double longitude)
{
List<StoreCategory> storeCategories = GetStoreCategories();
var request = CreateFoursquareStoreRequest
(latitude, longitude, maxRadius, returnLimit, storeCategories);
var response = await client.GetResponseAsync(request);
if (response.StatusCode == HttpStatusCode.OK)
{
// had to use json.net right now as the wrapper does not expose restsharps deserilizer
var venue = JsonConvert
.DeserializeObject<VenueSearch>(response.Content);
AddUpdateStores(storeCategories, venue);
}
else
{
ErrorSignal.FromCurrentContext()
.Raise(response.ErrorException);
}
}
public async Task<HttpResponseMessage>Get()
{
await storeService.PointsOfInterestNearUser(80, -130);
var product = productService.FindProduct("Noodles");
var allStores = storeService.FindStoresThatHaveItem(product);
return allStores;
}
When I watch from the debugger it looks like it is still all going in order. I think product and allStores need to be since I need the product before I can find the stores but PointsOfInterestNearUser should be going at the same time as FindProduct.
Edit 3
Here is my FindProduct Method. Not sure what to make async to me it looks like everything needs to wait.
public ResponseResult<Product> FindProduct(string barcode)
{
ResponseResult<Product> responseResult = new ResponseResult<Product>();
Product product = null;
try
{
var findBarCode = context.Barcodes.Where(x => x.Code == barcode).Select(x => x.Product).FirstOrDefault();
responseResult.Response = product;
if (product == null)
{
responseResult.Status.Code = HttpStatusCode.NotFound;
}
else
{
responseResult.Status.Code = HttpStatusCode.OK;
}
}
catch (SqlException ex)
{
ErrorSignal.FromCurrentContext().Raise(ex);
responseResult.Status.Code = HttpStatusCode.InternalServerError;
responseResult.Status.Message = GenericErrors.InternalError;
}
return responseResult;
}
Edit 4
Still not sure how to do the Task.WhenAll()
public async Task<HttpResponseMessage>Get()
{
Task[] tasks = new Task[2];
tasks[0] = storeService.PointsOfInterestNearUser(80, -130);
tasks[1] = productService.FindProduct("Noodles");
await Task.WhenAll(tasks);
// not sure how to get product back out. I looked in the debugger and saw a "Result" that has it but when I do tasks[1].Result inetllisene cannot find .Result
var allStores = storeService.FindStoresThatHaveItem(product);
return allStores;
}
I would recommend using async/await for this. Updating a cache is one of the rare situations where returning early from an ASP.NET request is acceptable. You can see my blog post on the subject for some helpful code.
So, something like this (simplified to just look up one "interesting place" per location):
public async Task<PlaceWithData> FindPlaceAsync(Location myLocation)
{
Place place = await GetPlaceFromFoursquareAsync(myLocation);
PlaceWithData ret = await GetExtraDataFromDatabaseAsync(place);
if (ret.NeedsRefresh)
BackgroundTaskManager.Run(() => UpdateDatabaseAsync(place, ret));
return ret;
}
You may also want to consider extending the ASP.NET caching system rather than doing a "roll your own" cache.