To be able to call Microsoft.Graph API on my .Net MAUI app, I need to get an access token. I followed this documentation:
https://learn.microsoft.com/en-us/graph/tutorials/dotnet?tabs=aad&tutorial-step=3
And here is my code:
internal class GraphHelper
{
private static string[] _graphUserScopes = new[] { "https://graph.microsoft.com/.default" };
// User auth token credential
private static DeviceCodeCredential? _deviceCodeCredential;
// Client configured with user authentication
private static GraphServiceClient? _userClient;
public static void InitializeGraphForUserAuth(Func<DeviceCodeInfo, CancellationToken, Task> deviceCodePrompt)
{
string adTenantId = "MY TENANT ID";
string adClientId = "MY CLIENT ID";
_deviceCodeCredential = new DeviceCodeCredential(deviceCodePrompt,
adTenantId, adClientId);
_userClient = new GraphServiceClient(_deviceCodeCredential, _graphUserScopes);
}
public static async Task<string> GetUserTokenAsync()
{
// Ensure credential isn't null
_ = _deviceCodeCredential ??
throw new NullReferenceException("Graph has not been initialized for user auth");
// Ensure scopes isn't null
_ = _graphUserScopes ?? throw new ArgumentNullException("Argument 'scopes' cannot be null");
// Request token with given scopes
TokenRequestContext context = new TokenRequestContext(_graphUserScopes);
AccessToken response = default;
try
{
response = await _deviceCodeCredential.GetTokenAsync(context);
}
catch (Exception ex)
{
}
return response.Token;
}
}
Call to await _deviceCodeCredential.GetTokenAsync(context) never comes back. And only in about 10 minutes the following exception is thrown:
Azure.Identity.AuthenticationFailedException: DeviceCodeCredential authentication failed: Verification code expired before contacting the server
I would like to know how I can diagnose and/or fix this problem.
Call to await ... never comes back.
await that never returns is a deadlock.
To find out where the problem starts, and fix it:
Put a breakpoint at start of GetUserTokenAsync.
Look at call stack. Check each method in call stack to see if it is declared async (you'll have to go to source code of each method).
What is the first method you encounter that is NOT declared async?
Look at the async method it calls: is there an await in front of that call? If not, that is your problem.
Starting a chain of async/await calls from a non-async context can cause a thread deadlock.
Typically happens on UI thread. Most common mistake is attempting to call inside a constructor.
IF it is the problem I describe, try adding await.
Build. If no compile error, it should now work.
If there is compile error at that await, a fix is to create an async context to do the await in.
To await on UI thread (MainThread):
// An alternative is "MainThread.BeginInvokeOnMainThread".
Dispatcher.Dispatch(async () =>
{
... = await ...;
// Code after your await line goes here.
}
// DO NOT have any code here, unless it is okay to run it BEFORE the dispatched await finishes.
To await on a background thread:
Task.Run(async () =>
... same logic as above ...
Related
I have two ways of retrieving secrets from my vault. One is async while the other is not.
Async:
public static async Task<string> GetSecret(string secretName)
{
try
{
return (await GetClient().GetSecretAsync("VaultURL", secretName)).Value;
}
catch (Exception ex)
{
return ex.Message;
}
}
Not async:
public static string GetSecretWithoutAwait(string SecretName)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
return keyVaultClient.GetSecretAsync("vaultUrl", SecretName).Result.Value;
}
Not having an async method suits my needs the most, and I prefer to keep it that way if possible. What are the consequences to making the retrieval a non-async process?
What are the consequences to making the retrieval a non-async process?
The Result member only exists on the Task<T> type; it does not exist on the Task type.
Like Wait, Result will synchronously block the calling thread until the task completes. This is generally not a good idea for the same reason it wasn’t a good idea for Wait: it’s easy to cause deadlocks.
If you have to block waiting the completion of an Async Task, use GetAwaiter().GetResult(). Wait and Result will wrap any exceptions within an AggregateException, which complicates error handling. The advantage of GetAwaiter().GetResult() is that it returns a normal exception instead of an AggregateException.
So I suggest that you use Async method to retrieve secrets
Using Audit.Net is it possible to create an audit scope for httpClient requests, in a similar way to the MVC.Core or WebAPI.Core Middleware?
I've tried something like this but not having much success, generally it'll timeout the app.
AuditScope scope = null;
try {
using(HttpClient client = new HttpClient) {
scope = await AuditScope.CreateAsync("",() => client)
// code to initialise the Httpclient
}
}
finally {
await scope.DisposeAsync();
}
I think the only option to hook into the HttpClient is to use a custom HttpClientHandler so you can intercept the rest calls.
Just as an example:
public class AuditClientHandler : HttpClientHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var options = new AuditScopeOptions()
{
EventType = $"{request.Method.Method} {request.RequestUri.AbsoluteUri}",
CreationPolicy = EventCreationPolicy.InsertOnStartReplaceOnEnd,
ExtraFields = new
{
request = GetRequestAudit(request)
}
};
using (var scope = AuditScope.Create(options))
{
var response = await base.SendAsync(request, cancellationToken);
scope.SetCustomField("response", GetResponseAudit(response));
return response;
}
}
}
I've used the InsertOnStartReplaceOnEnd creation policy, so the request is saved before it's sent to the server, and the response is added to the event and saved afterwards.
The implementation of GetRequestAudit / GetResponseAudit is up to you, just return an object (that can be serialized) with the information you want to log.
So each time you need to audit an HttpClient instance, you need to pass the handler to its constructor:
var cli = new HttpClient(new AuditClientHandler());
var response = await cli.GetAsync("http://google.com");
Anyway I will evaluate providing a new library (Audit.HttpClient?) with a configurable Handler so the implementation could be cleaner.
Update
You can now use the Audit.HttpClient extension for a cleaner implementation. Take a look at the documentation here
So I have this bit of code that gets a token from Azure Active Directory. When this bit runs the first time after the server starts, it's crashing with a null reference exception. The exception occurring is recorded in the output window, but the exception itself isn't caught by my code.
When I use the debugger to step through this code, it simply does not continue past AcquireTokenAsync - It terminates there, with the exception recorded in output, but it's not caught by my try/catch, and there's no way to recover.
There's nothing in here that's null, so I'm at a bit of a loss to explain, especially why this only occurs once after the server is restarted.
Code
private static async Task<string> getToken()
{
string aadInstance = ConfigurationManager.AppSettings["Authority"];
string tenant = ConfigurationManager.AppSettings["Tenant"];
string clientId = ConfigurationManager.AppSettings["ClientId"];
string clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
string resource = ConfigurationManager.AppSettings["GraphResourceUri"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
try
{
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCredential);
//anything past here never runs
return result.AccessToken;
}
catch (Exception ex)
{
Debug.WriteLine(ex.StackTrace);
}
return "";
}
Edit:
Okay I have a stack trace after messing with exception settings:
StackTrace " at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)"
You are experiencing a deadlock issue that you can overcome by using:
AuthenticationResult result = authContext.AcquireTokenAsync(resource, clientCredential).Result;
or
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCredential).ConfigureAwait(false);
This issue is caused by blocking on async code.
For a deeper dive into these topics, you can check here and here
The issues described in these links are happening somewhere internal to the call to await authContext.AcquireTokenAsync(resource, clientCredential); (code not posted so I can't tell you exactly where)
Is it possible to block a function call that returns a future?
I was under the impression calling .then() does it, but that's not what I'm seeing in my output.
print("1");
HttpRequest.getString(url).then((json) {
print("2");
});
print("3");
What I'm seeing in my output is:
1
3
2
The getString method doesn't have an async that would allow me to await it and then executes asynchronously in any case.
static Future<String> getString(String url,
{bool withCredentials, void onProgress(ProgressEvent e)}) {
return request(url, withCredentials: withCredentials,
onProgress: onProgress).then((HttpRequest xhr) => xhr.responseText);
}
How do I make it blocking without placing an infinite while loop before step 3 waiting for step 2 to be completed (not that it would work anyways due to the single thread nature of Dart)?
The above HttpRequest loads a config.json file that determines how everything works in the app, if the request for a field in the config is done before the config.json file is done loading, it causes errors, so I need to wait until the file is done loading before I allow calling getters on the fields of the class or getters needs to wait for the once-off loading of the config.json file.
Update, this is what I eventually did to make it work after Günter suggested I use a Completer:
#Injectable()
class ConfigService {
Completer _api = new Completer();
Completer _version = new Completer();
ConfigService() {
String jsonURI =
"json/config-" + Uri.base.host.replaceAll("\.", "-") + ".json";
HttpRequest.getString(jsonURI).then((json) {
var config = JSON.decode(json);
this._api.complete(config["api"]);
this._version.complete(config["version"]);
});
}
Future<String> get api {
return this._api.future;
}
Future<String> get version {
return this._version.future;
}
}
And where I use the ConfigService:
#override
ngAfterContentInit() async {
var api = await config.api;
var version = await config.version;
print(api);
print(version);
}
Now I get blocking-like functionality without it actually blocking.
There is no way to block execution until asynchronous code completes. What you can do is to chain successive code so that it is not executed before the async code is completed.
One way to chain is then
print("1");
HttpRequest.getString(url) // async call that returns a `Future`
.then((json) { // uses the `Future` to chain `(json) { print("2"); }`
print("2");
});
print("3"); // not chained and therefore executed before the `Future` of `getString()` completes.
An async call is just scheduling code for later execution. It will be added to the event queue and when the tasks before it are processed it itself will be executed. After an async call is scheduled the sync code `print("3") is continued.
In your case HttpRequest.getString() schedules a call to your server and registers (json) { print("2") as callback to be called when the response from the server arrives. Further execution of the application doesn't stall until the response arrives and there is no way to make that happen. What instead happens is that sync code is continued to be executed (print("3")).
If your currently executed sync code reaches its end, then the next scheduled task is processed the same way.
then() schedules the code (json) { print("2"); } to be executed after getString() completed.
await
async and await just make async code look more like sync code but otherwise it is quite the same and will be translated under the hood to xxx.then((y) { ... }).
I'm not sure I understand what you're trying to achieve, but it looks to me you want to do this:
myFunction() async {
print("1");
final json = await HttpRequest.getString(url);
print("2");
print("3");
}
async statement is only needed in the consumer function. In other words, producer functions doesn't need to have async, they only need to return a Future.
you should be able to do this:
Future consumerFunc() async {
print("1");
var response = await HttpRequest.getString(url);
print("2");
print("3");
}
and it should result:
1
2
3
Note: await replaces then methods
I am using Edge.js so that I can call Node.js from C#. According to the documentation in the link I would do that similar to the following:
[HttpPost]
public ActionResult Input(InputModel obj)
{
validateInput(obj);
return View();
}
private async void validateInput(object obj)
{
var func = Edge.Func(#"
return function (data, callback){
var username = data.username,
email = data.email;
callback(null, username);
}
");
ViewBag.Msg = (string)await func(obj);
}
However, I get the following run time error:
Additional information: An asynchronous operation cannot be started at this time.
Asynchronous operations may only be started within an asynchronous handler or module or during
certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the
Page is marked <%# Page Async="true" %>. This exception may also indicate an attempt to call an
"async void" method, which is generally unsupported within ASP.NET request processing. Instead,
the asynchronous method should return a Task, and the caller should await it.
My question is two-fold:
1.How do I make the page, async=true. I know how to do this for a web forms project but not a MVC project.
2.Is there a better way to do what I am trying to do? A red flag will probably go up when you see that I am returning void however this is do to the fact that Edge.js is being used. Even so, I have tried returning a Task and then task.Wait() in the calling method but the task never finishes.
After trying some different things, the following solution worked for me.
Even though I answered my own question, and it seems trivial, I am not removing this question as there are not a lot of knowledge on the web about Edge.js.
[HttpPost]
public async Task<ActionResult> Input(InputModel obj)
{
ViewBag.Msg = await validateInput(obj);
return View();
}
private async Task<string> validateInput(object obj)
{
var func = Edge.Func(#"
return function (data, callback){
var username = data.username,
email = data.email;
callback(null, username);
}
");
return (string)await func(obj);
}