I am using a API with a limit, there is a high possibility that I could hit the limit.
I am wondering how people handle this? Do they check if they hit the limit and then throw an exception? If so what type of exception?
Are there any best practices?
I am able to see if I hit the limit like below:
if (!string.IsNullOrEmpty(result.error))
{
// we have hit a limit
}
This API is used for a MVC application.
I am caching the ActionResult methods with the API content ([OutputCache]). If the Action method does not recieve the API result then the view will be empty, but if i throw something it will end up on the custom errors page.
You should first log what happened using the information you receive from result.error and then create a custom exception and throw it including the original error message.
if (!string.IsNullOrEmpty(result.error))
{
// we have hit a limit
// log error
// throw new MyException(result.error);
}
Normally you can't do much in this type of error so you log it, throw the exception and it's somebody's else problem. When the exception is caught, you could decide, based on the exception type, to wait and retry.
Related
I am using the MS Graph API from our web app to create an MS Teams Team in clients' systems and setup a few folders. But I will randomly get errors if I don't impose a hard-coded wait after creating the team. I call the following endpoints in the order shown:
//Create new Team and get basic info
POST teams
GET teams/{team-id}/primaryChannel
GET teams/{team-id}
GET teams/{team-id}/channels/{channel-id}/filesFolder
//Sometimes unknown users must be invited to join org as guest
POST invitations
//Everyone but the owner is added as a guest
POST teams/{team-id}/members
//This is done in a batch, because there is one folder per team guest + one for owner
POST groups/{team-id}/drive/items/{channel-folder-id}/children
//Team members' folders are permitted to them only. So all permissions are deleted and a single user added back
GET groups/{folder-id}/drive/items/{folder-id}/permissions
DELETE groups/{team-id}/drive/items/{folder-id}/permissions/{permission-id}
POST groups/{folder-id}/drive/items/{item-id}/invite
I will sporadically get Forbidden and/or Bad Request responses from:
POST teams/{team-id}/members
DELETE - groups/{team-id}/drive/items/{item-id}/permissions/{permission-id}
Obviously the return statuses of 403 are bugs, because the app definitely has permission to perform the action.
Imposing a 60 second wait after creating the Team seems to resolve this. However, I am currently testing on our Teams environment and am concerned that clients with larger Teams setups will require a longer wait period. I've seen other areas where the documentation says you should wait up to 15 minutes before using a Team that was created from a Group (I am not sure if this applies to creating a normal Team though).
Does anyone know what kind of latency I should be prepared for generally, and if there is any endpoint I can ping to see if the Team is ready for use?
Azure AD, Teams and Exchange are all different systems and need some kind of synchronization that sometimes needs some time.
Whenever you're going to create something in one of these systems, be prepared that it takes some time to access it.
One of the most awkward behaviour I came across is, when you create a group through Exchange Remote Powershell you'll get instantly the group object back. This object has an Azure Object ID. But if you immediately go to Graph and make a request for that group you'll get a 404. Also a look into Azure Portal shows nothing. But if you wait some time (minimum 30 secs, but up to 20!! minutes) the group suddenly appears.
The same also applies if you create a user in Azure through Graph. If you do this, you'll get back an object with the azure id. If you immediately try to add this user to a group or a directory role, it can also happen to get an error, but the timeout here is normally somewhere below 2 sec and I've never seen something above 10 secs.
So for everything, where I'm going to create something in Graph and immediately try to use it, I build some helper method, that tries it multiple times with some smaller timeout between each call:
internal static class Multiple
{
public static Task Try<TException>(int maxRetries, TimeSpan interval, Func<Task> task)
where TException : Exception
{
return Try<TException>(maxRetries, interval, task, exception => true);
}
public static async Task Try<TException>(int maxRetries, TimeSpan interval, Func<Task> task, Func<TException, bool> isExpectedException)
where TException : Exception
{
do
{
try
{
await task().ConfigureAwait(false);
return;
}
catch (Exception ex) when (ex.GetType() == typeof(TException) && isExpectedException((TException)ex))
{
maxRetries--;
if (maxRetries <= 0)
throw;
await Task.Delay(interval);
}
} while (true);
}
}
The usage of the class is as follows:
await Multiple.Try<ServiceException>(20, TimeSpan.FromSeconds(1), async () =>
{
educationClass = await serviceClient.Education.Classes[groupId.ToString()].Request().GetAsync();
}, ex => ex.Error.Code == "Request_ResourceNotFound");
This helper will call the inner method up to 20 times with a timeout of one second. Also the thrown exception must have the given error code. If the number of retries is exceeded or a different error is thrown, the call will rethrow the original exception and must be handled on a higher level.
Simply be aware that behind the Graph interface a highly distributed system works and it sometimes needs some time to get everything in sync.
I test it in my side and met same issues with yours. The 403 error should be a bug as you mentioned because I also have the permission to do the operation. But you mentioned that add guest user to owner, I test it with bad request response, I think it is by design.
Since you can request success after waiting 60 seconds, I think the solution is add a while loop in your code to request the graph api multiple times. In the while loop, if request fail, wait 10 seconds then request again(as Flydog57 mentioned in comments). But you also need to add a mechanism to break loop when request always fail in your code to avoid infinite loops.
I would like to ask you if it is possible to handle exception during localizing? For instance I have made recently mistake in my resx file, and my localizer caused that in one language users got error (in translated version I wrote something like "foo {2} bar" instead of "foo {0} bar". I would like to handle this exception that localizer returns the "not translated" string.
Is it possible to write some kind of special exception handler for localizer? Should it implements any interface or something?
Many thanks for your answer.
I think you might need to custom a Resource filters with OnResourceExecuted().
Filter types
Each filter type is executed at a different stage in the filter pipeline:
Authorization filters
run first and are used to determine whether the user is authorized for the request. Authorization filters short-circuit the pipeline if the request is not authorized.
Resource filters
Run after authorization.
OnResourceExecuting runs code before the rest of the filter pipeline. For example, OnResourceExecuting runs code before model binding.
OnResourceExecuted runs code after the rest of the pipeline has completed.
Action filters
Run code immediately before and after an action method is called. Can change the arguments passed into an action. Can change the result returned from the action. Are not supported in Razor Pages.
Exception filters
apply global policies to unhandled exceptions that occur before the response body has been written to.
Result filters
run code immediately before and after the execution of action results. They run only when the action method has executed successfully. They are useful for logic that must surround view or formatter execution.
The following diagram shows how filter types interact in the filter pipeline.
i use a simpler way without any middle ware ,i translate the exception messages into localized string as below ,
call the method in your catch with the original exception message
catch (Exception dex)
{
return Json(new { success = false, responseText = Models.SiteUtils.FriendlyErrorMessage(dex) });
}
method for localization of error messages ,will search for the exception message and get your version of it ,
notice i use Message.Contains(x.OriginalError) so i do not need to translate he whole message exactly ,you may decide to use exact match if you need based on your own logic
public static string FriendlyErrorMessage(Exception dex)
{
var Message = dex.Message;
var listOfStrings = new tblErrorMsgMappingVM().GetAll().ToList();
var ErrorMessage = listOfStrings.Find(x => Message.Contains(x.OriginalError));
if (ErrorMessage != null)
{
Message = ErrorMessage.ErrorMessage;
}
else
Message = "Unknown Error ,pelase review the log";
var value = new tblLocalizationRecordsVM().GetResourceValue(Message, "ErrorMsg");
return value;
}
I've added LinkedIn as a provider. I have implemented the login and register with LinkedIn without any issue. In the use case where the user CANCELS from within the provider Pages (either linkedIn login or cancels the authorization of the app) the identity middleware seems to throw an unhandled exception:
An unhandled exception occurred while processing the request.
Exception: user_cancelled_login;Description=The user cancelled LinkedIn login
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
System.Exception: user_cancelled_login;Description=The user cancelled LinkedIn login
Exception: An error was encountered while handling the remote login.
The provider setup in startup defines the callback:
services.AddAuthentication().AddOAuth("LinkedIn", "LinkedIn", c =>
{
c.ClientId = Configuration["Authentication:LinkedIn:ClientId"];
c.ClientSecret = Configuration["Authentication:LinkedIn:ClientSecret"];
c.Scope.Add("r_basicprofile");
c.Scope.Add("r_emailaddress");
c.CallbackPath = "/signin-linkedin";
....
And As I have said the middleware seems to handled ALL other cases except where the user cancels within the LinkedIn pages. The return URL from LinkedIn looks correct:
https://localhost:44372/signin-linkedin?error=user_cancelled_login&error_description=The+user+cancelled+LinkedIn+login&state=CfDJ8MA7iQUuXmhBuZKmyWK9xeAgBBkQvnhf1akLhCIn9bsajCPUf7Wg22oeZBH9jZOIY3YrchMSWZ4dH7NQ1UngLKEuqgU-IHfBglbgJDtS-wc4Z-6DnW66uR0G1ubvNVqatFJHYv17pgqZT98suVkvKgihcJdbNEw7b1ThkuFbn9-5EcYhQ5ln6ImoTgthT8io1DOcCfc_-nBVfOa93a6CpUJTsZc9w93i70jn5dKKXSLntZe0VyRSA0r0PKc5spu5En-0R1rxiLjsjo4dy89PV3A
But never gets to my ExternalCallback controller method where the other cases like successful login/authorization are handled??
I'm wondering if this is working for anyone else with 3rd part providers?
There's a Github issue that explains what's happening here in more detail, with a bit of information as to why it's happening and even an indication that this won't be "fixed":
Handling the RemoteFailure event is the right thing to do. We should update our docs/samples to show how to handle that event and at least show a more appropriate message to the user. The error page sample could include a link to enabled the user to try logging in again.
Unfortunately it's difficult to implement this event in a very generic way that's also super useful because each remote auth provider has its own behavior for different types of failures.
The workaround for this (as quoted above) is to handle the RemoteFailure event:
services.AddAuthentication().AddOAuth("LinkedIn", "LinkedIn", c => {
// ...
c.Events.OnRemoteFailure = ctx =>
{
// React to the error here. See the notes below.
return Task.CompletedTask;
}
// ...
});
ctx is an instance of RemoteFailureContext, which includes an Exception property describing what went wrong. ctx also contains a HttpContext property, allowing you to perform redirects, etc, in response to such exceptions.
I've found the following to work well for me, based on this and similar to Kirk Larkin's answer. The part that took a little figuring out was where to redirect to, without causing problems for subsequent login attempts.
services.AddAuthentication().AddOAuth("LinkedIn", "LinkedIn", c =>
{
...
c.Events = new OAuthEvents()
{
OnRemoteFailure = (context) =>
{
context.Response.Redirect(context.Properties.GetString("returnUrl"));
context.HandleResponse();
return Task.CompletedTask;
}
};
};
I have successfully implemented a connection to ServiceBus with MessageReceiver using RegisterMessageHandler that starts a pump (from this example) and all seems to work just fine.
But in case of exception like e.g. when I turn off network connection the pump throws exceptions continuously to the ExceptionHandler. Every second or even faster. I am wondering if this is supposed default behavior and more importantly if it's possible to change, so that e.g. connection retries can happen every 1 minute. Or am I supposed to do Thread.Sleep or something to achieve that?
receiver.RegisterMessageHandler(
async (message, cancellationToken1) => await HandleMessage(receiver, message),
new MessageHandlerOptions(HandleException)
{
AutoComplete = false,
MaxConcurrentCalls = 1
});
P.S. This is how I solved it now, but not sure if it's a proper way:
private Task HandleException(ExceptionReceivedEventArgs args)
{
_logger.Error(...);
return Task.Delay(60000);
}
P.S Here is the RetryPolicy.Default dump:
Azure Service Bus has a default retry policy (RetryPolicy.Default), but given the transport is trying to receive messages and the broker is not available, will raise exceptions.
ExceptionReceivedContext provides a context, ExceptionReceivedContext which has an action that has failed, and the original exception. You can evaluate the action and decide what needs to be done. You could also check if the exception is transient or not. For transient errors, based on the action, you could just wait for the message to be retried again later (Receive action). In other cases you could either log an error or take a more specific action.
Try to configure the "RetryExponential" on your "SubscriptionClient" like this:
var receiver = new Microsoft.Azure.ServiceBus.SubscriptionClient(_serviceBusConnString, _topic, _subscription, this._receiveMode, new RetryExponential(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), _retryPolicyMaximumRetryCount));
This is the parameters descriptions:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.servicebus.retryexponential?view=azure-dotnet
Here other post about what the properties means:
ServiceBus RetryExponential Property Meanings
I have a set of WCF web services, which, if an exception occurs, will use OutgoingWebResponseContext to return the error description back to the caller.
My problem is that sometimes, when something goes wrong, the web service is shown as failed, with an ERR_CONNECTION_RESET message, rather than returning the error message, and I would like to prevent this from happening.
For example, here's my beautiful C# code which queries my Northwind database, and returns a list of Customer names.
Or rather, it would do, but I've deliberately stuck an exception in there.
public List<string> GetAllCustomerNames()
{
// Get a list of unique Customer names.
//
try
{
throw new Exception("Oh heck, something went wrong !");
NorthwindDataContext dc = new NorthwindDataContext();
var results = (from cust in dc.Customers select cust.CompanyName).Distinct().OrderBy(s => s).ToList();
return results;
}
catch (Exception ex)
{
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
response.StatusDescription = ex.Message;
return null;
}
}
In my AngularJS controller, I can call this web service...
$http.get('http://localhost:15021/Service1.svc/getAllCustomerNames')
.then(function (data) {
// We successfully loaded the list of Customer names.
$scope.ListOfCustomerNames = data.GetAllCustomerNamesResult;
}, function (errorResponse) {
// The WCF Web Service returned an error
var HTTPErrorNumber = errorResponse.status;
var HTTPErrorStatusText = errorResponse.statusText;
alert("An error occurred whilst fetching Customer Names\r\nHTTP status code: " + HTTPErrorNumber + "\r\nError: " + HTTPErrorStatusText);
});
...and if this exception occurs, I do see the full exception message...
Wonderful.
For all readers, who've wanted to know a simple, generic way to return and display Exceptions from your WCF web services, there's your solution !
Okay, so now I'll remove my Exception line of code, and the web service runs fine again. The Customer data is returned to the Angular controller without any problems.
But.. if an exception occurs when connecting to the database (for example, it times out, I have the wrong database name in my connection string, etc), then the following line of code does throw an exception...
NorthwindDataContext dc = new NorthwindDataContext();
...my catch code does kick in (I put a breakpoint on it to check), and we set the StatusDescription to the Exception message..
But the response which is sent back doesn't contain either my HTTP Status number or text.
My Angular controller just receives an HTTP Status Code of 0, with no StatusMessage.
And if I hit F12 in Chrome, you can see that it actually says the web service failed, rather than returning Status Code 403, and there's no message.
So my question is simply, is there a way to prevent this failure from happening ?
Obviously, its much friendlier to return a description of what went wrong, rather than just "The web service failed miserably... but we can't tell you why."
If I can get over this issue, this'd be a nice way to make all of our WCF web services error-message friendly in our in-house apps. But of course, for production systems, sure, you wouldn't really want to bombard the user with technical exception messages.
Update, many hours later..
I found the cause, and the solution.
Are you ready for this?
The problem was that when the DataContext line threw an exception...
NorthwindDataContext dc = new NorthwindDataContext();
...the exception message had a line-feed in it.
Cannot open database "Northwind" requested by the login.
The login failed.Login failed for user 'MikesWrongUserName'.This session has been assigned a tracing ID of '1f0513d1-9ce1-47ef-9d44-1f4546eb0b73'. Provide this tracing ID to customer support when you need assistance.
That line-feed was causing the problem. If you attempt to return that in a StatusDescription field, it causes the entire web service to be reported as "failed".
So, the solution is to simply remove any line-feeds before you return the exception message.
So here, finally, is an example of how to use a try..catch in your WCF web services, so they'll always return exception messages back to the caller.
public List<string> GetAllCustomerNames()
{
// Get a list of unique Company Names.
//
try
{
NorthwindDataContext dc = new NorthwindDataContext();
var results = (from cust in dc.Customers select cust.CompanyName).Distinct().OrderBy(s => s).ToList();
return results;
}
catch (Exception ex)
{
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
// If a database Exception occurs, it might have a line-feed in it,
// which will cause the web service to completely fail (no Status
// Code or Status Message will get returned.)
response.StatusDescription = ex.Message.Replace("\r\n", "");
return null;
}
}
Thanks for all of your suggestions!