3

I'm currently testing the MS Graph .NET Core SDK Client in my .NET Core 3.1 app. Purpose is to provide my own local Web API user service that performs updates/changes/fetching of users from Azure B2C AD.

In my intended solution, I will have various HTTP Client micro-services that will call API commands to my user service SDK Client, as opposed to calling the Azure Graph API using REST directly.

In my user service app, I'm trying to keep things clean by using a repository/interface approach for the actual SDK commands that are sent off to Azure. This same user service app is then returning the data back to my local HTTP Clients using my own WEB API. Imagine this user service app as the man in the middle effect.

Image below to summarize the environment:

enter image description here

The purpose of this is to reduce the work when changes or additions are made to the features used with the Graph Service i.e provide some standardization in comms across my own local apps and to promote better separation of concerns. Also if MS make changes to the Graph API, then I'm only updating the user service app rather than making code changes across all my HTTP Client apps. Hope that makes sense.

Anyway, now to the point! reason for my questions:

(bear with me here, I'm new to REST and using Interfaces)

Any errors encountered between the Graph SDK Client and the Graph Service API in Azure will be logged within the my user service app i.e. the extensive json error details i will capture at the first opportunity, HOWEVER I simply don't need to pass this level of detail back to all my local calling HTTP Clients.

What I'm trying to achieve is a means of identifying/capturing any HTTP Status code errors that were encountered between the SDK Client and the Graph Service, along with perhaps some basic details of the error i.e a short description and only pass these lower level details back to my local HTTP Clients i.e, keep things clean.

I'm struggling with knowing how to do this in my code, especially given the fact that i'm using an interface at the same time is making it more complex. The MS docs does give info on the list of expected error codes to be had from using the graph service, but there is no examples that explains how to process this information in order to pass the relevant (but lighter version of the info) back to another source.

Example scenario:

  1. Local HTTP Clients call [HttpGet] GetUserById to my user service Web API
  2. My Web API then uses the Graph SDK Client to reach out to Azure B2C, fetch the data and return.
  3. My WEB API should then inspect the info received, if a user if found then great, return the user to my calling HTTP Client.
  4. IF the user was not found, or perhaps a bad request was made (missing attributes / wrong user id etc) then I need to inspect the HTTP Status error code received and pass this same error code back to my calling HTTP Client, at the same time, rather than passing back the extensive json error details received from Graph Service, I just to want pass back a more simplified message, such as user not found.
  5. The logical approach for the simplified message would be to utilize the already baked in codes messages provided by the Graph Service:

Like below: The "code" attribute is enough to explain the situation to any calling HTTP Client, if I need to investigate further then I would inspect the logs:

 {
   "error": {
     "code": "invalidRange",
     "message": "Uploaded fragment overlaps with existing data.",
     "innerError": {
       "requestId": "request-id",
       "date": "date-time"
     }
   }
 }

I just cant work out how to extract the HTTP Status code received from the the calls to the Graph Service through the SDK, as well as being able to fetch a single attribute from the json error message in the body and then return this reduced/simplified info back to my own HTTP Clients in the correct and conform ant manner. My own project code so far is below:

My user service WEB API Controller:

    [HttpGet("{id}")]
    public async Task<IActionResult> GetUserById(string id)
    {
        try
        {
            var user = await _repository.GetUserByIdAsync(id);

        }
        catch(Exception ex) 
        {
            // What am I catching here? how do I get the HTTP Status Code that 
            was received from the call to the Graph Service in the event of an error?
            // How do i extract the code error key/value from the json received from Graph Service?
        }

        // Finally, how do I return this captured info to the HTTP Client?


        // Ignore this part, not sufficient for my needs.   
        //if (user == null) return BadRequest();
        //if (user != null) return Ok(user);
        //else return NotFound();
    }

My Interface:

namespace MicrosoftGraph_API.Repository
{
    public interface IGraphClientRepo
    {
        public Task<List<User>> GetAllUsersAsync();

        public Task<User> GetUserByIdAsync(string id); 
    }
}

My Graph SDK Client Class:

public class GraphSDKClientRepo : IGraphClientRepo
{
    public readonly IUserServiceClient _IUserServiceClient;

    public GraphSDKClientRepo(IUserServiceClient userServiceClient)
    {
        _IUserServiceClient = userServiceClient;
    }

    public async Task<User> GetUserByIdAsync(string id)
    {
        var graphClient = _IUserServiceClient.InitializeGraphClient();

        // Get user by object ID
        var result = await graphClient.Users[id]
            .Request()
            .Select(e => new
            {
                e.DisplayName,
                e.Id,
                e.Identities
            })
            .GetAsync();

        return result;
    }
}
Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
OJB1
  • 2,245
  • 5
  • 31
  • 63

1 Answers1

5

If your call encounters an error, the SDK will throw a ServiceException. This class includes the property you're looking for:

/// <summary>
/// The HTTP status code from the response.
/// </summary>
public System.Net.HttpStatusCode StatusCode { get; }

So your call to Graph would look something like:

public async Task<User> GetUserByIdAsync(string id)
{
    var graphClient = _IUserServiceClient.InitializeGraphClient();

    // Get user by object ID
    try
    {
        return await graphClient
            .Users[id]
            .Request()
            .Select("id,displayName,identities")
            .GetAsync();
    }
    catch (ServiceException)
    {
        throw;
    }
}

And you're Controller code would look something like


[HttpGet("{id}")]
public async Task<IActionResult> GetUserById(string id)
{
    try
    {
        return this.Ok(await _repository.GetUserByIdAsync(id));
    }
    catch (ServiceException ex)
    {
        return this.StatusCode(se.StatusCode);
    }
}

If you need to handle exceptions differently by HTTP Status, you can do that as well

[HttpGet("{id}")]
public async Task<IActionResult> GetUserById(string id)
{
    try
    {
        return this.Ok(await _repository.GetUserByIdAsync(id));
    }
    catch (ServiceException e) when(e.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        //
        // Do "Not Found" stuff
        //
        return this.StatusCode(e.StatusCode);
    }
    catch (ServiceException e) when(e.StatusCode == System.Net.HttpStatusCode.BadRequest)
    {
        //
        // Do "Bad Request" stuff
        //
        return this.StatusCode(e.StatusCode);
    }
    catch (ServiceException e)
    {
        return this.StatusCode(e.StatusCode);
    }
}
Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
  • Hi Marc, many thanks for coming back to me. I'm getting a squiggly line underneath 'return this.StatusCode(se.StatusCode);' in the controller method, the message says "does not exist in the current context. I tried changing se. to ex. in case it was typo but this didn't fix it? Also how would do i extract the error message details from within the json body? assuming I need to use json.deserialize of some form, thanks – OJB1 Jun 18 '20 at 21:21
  • Think I fixed this part: I had to modify the controller method slightly from your own example: "var statusCode = (int)((ServiceException)ex).StatusCode;" and then underneath I put "return this.StatusCode(statusCode)" – OJB1 Jun 18 '20 at 21:39