Search code examples
c#microsoft-graph-apihangfire

Making Microsoft Graph API call with Hangfire


I am struggling to make graph API calls using Hangfire background jobs with delay as i believe the HttpContext is null when it comes to making the call and i'm not sure how to get around this issue.

// setting job to fire with delay

BackgroundJob.Schedule(() => this.HangfireTest("test@gmail.com", false), TimeSpan.FromSeconds(20));

// make the graph api call, it comes back with a result of 'Not yet computed'

var userInfo = this.graphServiceClient.Me.Request().Select(Info.Fields).GetAsync();

i've tried passing in the context but that doesn't work. Not sure how to go around this issue. thanks!


Solution

  • This is an ASP.Net Core 3.1 MVC prototype implementation that is working for me. I too had a lot of issues finding a solution hence posting this here. I have edited some of the code out to make it easier to read and remove bloat not required to solve this problem.

    Note the approach is to retrieve an access token from GraphAPI and pass that to the background job, creating a new stand-alone GraphClient instance and adding the required bearer header containing the access token.

    In startup.cs, EnableTokenAcquisition to allow retrieval of access token in controller.

    services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(this.Configuration.GetSection("AzureAd"))
        .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
        .AddMicrosoftGraph(this.Configuration.GetSection("DownstreamApi"))
        .AddInMemoryTokenCaches();
    

    Controller init...

    using Microsoft.Graph;
    using Microsoft.Identity.Web;
    
    private readonly GraphServiceClient graphServiceClient;
    private readonly ITokenAcquisition tokenAcquisition = null;
    
    public HomeController(ILogger<HomeController> logger, IConfiguration iconfiguration, GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
    {
        this.graphServiceClient = graphServiceClient;
        this.tokenAcquisition = tokenAcquisition;
    }
    

    Schedule job controller action

    public IActionResult ScheduleJob(BackgroundJobsViewModel model)
    {
        try
        {
            string accessToken = string.Empty;
    
            try
            {
                accessToken = Task.Run(async () => await this.tokenAcquisition.GetAccessTokenForUserAsync(scopeList)).Result;
            }
            catch (MicrosoftIdentityWebChallengeUserException ex)
            {
                Task.Run(async () => await this.tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopeList, ex.MsalUiRequiredException));
            }
            catch (Microsoft.Identity.Client.MsalUiRequiredException ex)
            {
                Task.Run(async () => await this.tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopeList, ex));
            }
    
            if (!string.IsNullOrEmpty(accessToken))
            {
    
                // Recurring Job.
                RecurringJob.AddOrUpdate(job.JobName, () => UpdateUsersBackroundJob.UpdateUsers(this.currentUser.Account, accessToken), $"{seconds} {minutes} {hours} {day} {month} {week}");
            }
            else
            {
                return this.View("BackgroundJobs", model);
            }
        }
        catch (Exception ex)
        {
            var errorModel = this.HandleException(ex);
            return this.View("Error", errorModel);
        }
    }
    

    Background job class and method...note Info.Fields I have not shown.

    using Microsoft.Graph;
    using Microsoft.Identity.Web;
    
    /// <summary>
    /// Class to encapsulate the background job to update users.
    /// </summary>
    public class UpdateUsersBackroundJob
    {
        /// <summary>
        /// Gets or sets the Graph Client.
        /// </summary>
        private static GraphServiceClient GraphClient { get; set; }
    
        public static void UpdateUsers(string upnName, string graphApiAccessToken)
        {
            if (string.IsNullOrEmpty(graphApiAccessToken))
            {
                throw new ApplicationException("Cannot run the update users background job without specifying an access token first.");
            }
    
            // Create our own new graph client using the provided token.
            GraphClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) =>
            {
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", graphApiAccessToken);
                return Task.FromResult(0);
            }));
            
            try
            {
    
                    // Update DB User data from GraphAPI Azure AD
                    var userInfo = Task.Run(async () => await GraphClient.Me.Request().Select(Info.Fields).GetAsync()).Result;
                    
                    ...do something with userInfo in application.
                }
                catch
                {
                    ...Handle exception.
                }
            }
        }
    }