-
Notifications
You must be signed in to change notification settings - Fork 234
Prototype of new API with the Graph and Downstream web API services #431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prototype of new API with the Graph and Downstream web API services #431
Conversation
-------------------------------------------- 1. Rename CallsWebApi in CallsWebApis 2. Rename CallsMicrosoftGraph into AddMicrosoftGraphServiceClient as this is what the developer will use in the controller, under CallsWebApis, because otherwise we'd have to re-pass the initialization for CallsWebApis (the ConfidentialClientApplicationOptions) 3. Updates the tests to get a feel of the API.
- Downstream API support folder contains options, service, extensions - Aligning the properties for Microsoft Graph options and Downstream API options Updated the Blazor sample to use both the Graph service and the Downstream API service (BlazorServer calls graph) Updated the WebAppCallsGraph service Todo list sample: - changed the namespace of the model (common to client and service). Ideally we'd put in a shared project. We might do that later. - Updated the TodoList service to leverage the DownstreamWebApi service. The code is way simpler!!! - updated the AppSettings.json and the Startup.cs to leverage these services - Fixed the Controller of the Web API which was not using the display name when adding an item.
.CallsWebApi() | ||
.CallsWebApis() | ||
.AddMicrosoftGraphServiceClient() | ||
.AddMicrosoftGraphServiceClient(Configuration.GetSection("GraphBeta")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really one or the other of these 3 lines;
{ | ||
_todoListService = todoListService; | ||
_todoListService = new TodoListService(downstreamWebApi); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too satisfied it, but I did not yet manage to inject the DownstreamApiService.
If we decided to go with this design, we'll have to fix that.
@@ -3,7 +3,7 @@ | |||
|
|||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using TodoListService.Models; | |||
using TodoList.Models; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the namespace because:
- it's common to the service and client (it's even the same file with a link)
- There is was a naming conflict between TodoListService (the namespace for this models), and the TodoListService class in the TodoListClient.
{ | ||
builder.AddDownstreamApiService(TodoListService.ServiceName, configuration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just leverage the generic DownstreamApiService
} | ||
|
||
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}."); | ||
return await _downstreamWebApi.CallWebApiForUserAsync<Todo, Todo>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use strongly typed wrappers and express just what is different. The code is way simpler and no copy/paste any longer.
@@ -23,7 +23,6 @@ | |||
|
|||
<ItemGroup> | |||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="3.1.1" /> | |||
<PackageReference Include="Microsoft.Graph" Version="1.16.0" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Microsoft.Graph is now included in Microsoft.Identity.Web. We don't need to reference it any longer in the samples
@@ -1,4 +1,4 @@ | |||
@model TodoListService.Models.Todo | |||
@model TodoList.Models.Todo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consequence of the renaming of namespace ..
@@ -76,7 +77,7 @@ public IActionResult Post([FromBody] Todo todo) | |||
{ | |||
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); | |||
int id = TodoStore.Values.OrderByDescending(x => x.Id).FirstOrDefault().Id + 1; | |||
Todo todonew = new Todo() { Id = id, Owner = HttpContext.User.Identity.Name, Title = todo.Title }; | |||
Todo todonew = new Todo() { Id = id, Owner = User.GetDisplayName(), Title = todo.Title }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol! we forgot this one when we updated the GetAsync()
@@ -33,7 +33,7 @@ public void ConfigureServices(IServiceCollection services) | |||
// Adds Microsoft Identity platform (AAD v2.0) support to protect this Api | |||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) | |||
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd") | |||
.CallsWebApi() | |||
.CallsWebApis() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renaming to express that this is a hub for downstream APIs.
Note that CallsWebApis() really provides the ITokenAcquisitionService .... but I don't think we want to write that?
@@ -284,7 +284,7 @@ public async Task AddMicrosoftWebAppCallsWebApi_WithConfigActionParameters() | |||
|
|||
var builder = services.AddAuthentication() | |||
.AddMicrosoftWebApp(_configureMsOptions, null, OidcScheme) | |||
.CallsWebApi(_configureAppOptions, initialScopes); | |||
.CallsWebApis(_configureAppOptions, initialScopes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See, in this case, if we had chosen the other design, we'd have to pass-in the _configureAppOptions in all the CallsMicrosoftGraph and CallsDownstreamApis, with a risk of errors (pass-in different things, whereas this would be the same service, really), and duplication of code.
"ClientSecret": "secret-goes-here" | ||
"ClientSecret": "fvHwfoLA8Zls6Huj5MBUQUDrJ5HXrauFQKDd2CacxJQ=" | ||
}, | ||
"GraphBeta": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the case of a sample that calls several sevices:
- Graph
- And a downstream API (which turns out to be Graph v1.0, but could be something else.
.CallsWebApi(scopes) | ||
.CallsWebApis() | ||
.AddMicrosoftGraphServiceClient(Configuration.GetSection("GraphBeta")) | ||
.AddDownstreamApiService("CalledApi", Configuration.GetSection("CalledApi")) | ||
.AddInMemoryTokenCaches(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the kind of code that you would have if you want to call several services.
If you want to use ITokenAquisition directly, just use .CallsWebApis()
Otherwise:
- if you want to use the MicrosoftGraphServiceClient wrapper, use
.AddMicrosoftGraphServiceClient
under.CallsWebApis()
- if you want to use one or several instances of the downstream API service (they have a name, which here, I've chosen the same as the section in the config file to make it simpler, but that's not absolutely necessary), you woud have one or several calls to
.AddDownstreamApiService("NameOfTheService", Configuration.GetSection("SectionInConfig"))
Then in the controller, you'd use IDownstreamWebApi.CallsWebApiForUser("NameOfTheService", and have the possiblity to override the configuration from the config file with a delegate.
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
try | ||
{ | ||
apiResult = await downstreamAPI.CallWebApiAsync("me"); | ||
response = await downstreamAPI.CallWebApiForUserAsync( | ||
"CalledApi", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we call the service "CalledApi", on behalf of the user (this maps to some configuration for this service: see below), and we override the relative path to call the "me" endpoint. All the other configurations are overridable as well.
if (response.StatusCode == System.Net.HttpStatusCode.OK) | ||
{ | ||
apiResult = await response.Content.ReadAsStringAsync(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we use the pattern recommended by Damien, but we could also use the strongly typed wrapper CallWebApiForUserAsync<InputType, ReturnType> which makes it even easier. It's used in the TodoListService wrapper in the TodoListClient project.
- Cleaning-up renaming done with Damien on the types
src/Microsoft.Identity.Web/DownstreamApiSupport/CalledApiOptions.cs
Outdated
Show resolved
Hide resolved
|
||
/// <inheritdoc/> | ||
public async Task<HttpResponseMessage> CallWebApiForUserAsync( | ||
string optionsInstanceName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: should we stick w/consistency of the naming? MicrosoftIdentityCallWebApiForUserAysnc()
....i don't like this much, but i think things should feel like they are part of MS identity web, or not...again, will need to think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of feel like I would leave this one as is.
My thought is that we should be careful adding prefixes in too many places since that can create a lot of noise (from my experience).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here this is a verb (method) on a class that we own, not an extension method on a class that we don't own (like Service Collection). I don't think it's abiguous. I would stick to CallWebApiForUserAsync
in this particular case
src/Microsoft.Identity.Web/DownstreamApiSupport/CalledApiOptions.cs
Outdated
Show resolved
Hide resolved
/// <summary> | ||
/// Base URL for the called Web API. For instance <c>"https://graph.microsoft.com/beta/".</c>. | ||
/// </summary> | ||
public string BaseUrl { get; set; } = "https://graph.microsoft.com/v1.0"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a general options class, should the BaseUrl
default be an empty string instead then?
|
||
/// <inheritdoc/> | ||
public async Task<HttpResponseMessage> CallWebApiForUserAsync( | ||
string optionsInstanceName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of feel like I would leave this one as is.
My thought is that we should be careful adding prefixes in too many places since that can create a lot of noise (from my experience).
jsoncontent = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); | ||
// case of patch? jsoncontent = new StringContent(jsonRequest, Encoding.UTF8, "application/json-patch+json"); | ||
} | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this else
is unnecessary, jsoncontent
would still be null here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried but the compiler was not happy line 131.
} | ||
|
||
/// <inheritdoc/> | ||
public async Task<HttpResponseMessage> CallWebApiForAppAsync( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also want to have a generic typed version for this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more work.
/// <returns>The value returned by the API.</returns> | ||
public Task<TOutput> CallWebApiForUserAsync<TInput, TOutput>( | ||
string optionsInstanceName, | ||
TInput input, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
input
> requestContent
?
Also maybe TInput
, TOutput
to TRequestContent
, TResponseContent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No sure about this one. The content is the json. Here input and output are object types (like Todo)
src/Microsoft.Identity.Web/DownstreamApiSupport/IDownstreamWebApi.cs
Outdated
Show resolved
Hide resolved
…ilderRefactoringToPrepareServices
This can be closed, correct? @jmprieur you did two smaller PRs instead right? |
No, there are the templates still |
Proposal for Microsoft.Graph support
CallsWebApi
inCallsWebApis
, as this becomes a hub for all what calls a Web API. The alternative design would have been to have CallsMicrosoftGraph and CallsDownstreamApi under AddMicrosoftIdentityWebApp / WebApi, however, this would have obliged the developer =to pass-in theConfidentialClientApplicationOptions
to each of the services they want to call (that is several times if they call several services, for instance both Graph, and a DownstreamApi for ARM, and a Downstream for https://myWebApi.This is now possible having several sections in the appsettings.json. See for instance the Blazor sample which call graph and another API (Graph really, but that would not matter)
CallsMicrosoftGraph
intoAddMicrosoftGraphServiceClient
as GraphServiceClient this the type that the developer will use in the controller and is what they will look for in the documentation. This method is present under CallsWebApis, because otherwise we'd have to re-pass the initialization for CallsWebApis (the ConfidentialClientApplicationOptions)Proposal for Downstream web API support
Todo list sample