Я делаю запрос GET, чтобы вернуть все приложения с их идентификатором и датой истечения срока действия пароля. Я работал с Graph Explore, который предлагает Microsoft, и мне это очень помогло. Код, который у меня есть, возвращает HTTP-запрос, а затем при попытке десериализации он терпит неудачу с приведенной ниже ошибкой. Я потратил много времени на SOF, просматривая аналогичный пост, но, похоже, ничего не работает. Я чувствую, что, возможно, здесь что-то упущено или я делаю это неправильно.
Я также использую более старую версию Graph Nugget pkg v4.41.0, а Core — v2.0.13. Я пошел их обновить, и в приложении было много критических изменений, и у меня пока нет времени все переписывать.
Я получаю следующую ошибку:
"Message": "An error has occurred.", "ExceptionMessage": "Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.List`1[Microsoft.Graph.Application]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath '['@odata.context']', line 1, position 18.", "ExceptionType": "Newtonsoft.Json.JsonSerializationException", "StackTrace": " at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)\r\n at Newtonsoft.Json.Serialization...
public async Task<List<Microsoft.Graph.Application>> GetAppExpList()
{
List<Microsoft.Graph.Application> apps = null;
// query string for url call
var url = this.GetGraphUrl($"{Consts.GraphUrl}?$select=id,passwordCredentials&$format=json");
// tried to do this with the built in Graph methods
List<QueryOption> options = new List<QueryOption>
{
new QueryOption("$select", "id,DisplayName,passwordCredentials"),
new QueryOption("$format", "json")
};
// Here I wanted to see what was brought back. No PasswordCreds for some reason
var test = await _graphClient.Applications.Request(options).GetAsync();
// GET request to the Applications Graph Api
var response = await this.HttpHandler.SendGraphGetRequest(url);
// Returns JSON data
if (response.IsSuccessStatusCode)
{
// Shows the correct data in JSON Format
var rawData = await response.Content.ReadAsStringAsync();
// Throws error above.
apps = JsonConvert.DeserializeObject<List<Microsoft.Graph.Application>>(rawData);
}
return apps;
}
Вот JSON, который возвращается для var rawData = await response.Content.ReadAsStringAsync();
{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#applications(id,passwordCredentials)"
,"value":[
{
"id":"00000000-0000-0000-0000-000000000000",
"displayName":"App 1 name Here",
"passwordCredentials":[]
},
{
"id":"00000000-0000-0000-0000-000000000001",
"displayName":" App 2 name here",
"passwordCredentials":[]
},
{
"id":"00000000-0000-0000-0000-000000000002",
"displayName":"App 3 name here",
"passwordCredentials":[
{
"customKeyIdentifier":null,
"displayName":"secret",
"endDateTime":"2025-01-30T14:46:40.985Z",
"hint":"oHI",
"keyId":"00000000-0000-0000-0000-0000000000",
"secretText":null,
"startDateTime":"2023-01-31T14:46:40.985Z"
}
]
}
]
}
извините, я обрезал, потому что есть куча приложений. Я отформатирую его правильно
Для «взаимодействия» вы можете попробовать Collection<> вместо List<>.
Я попробовал код _graphClient.Applications.Request(options).GetAsync() с той же версией SDK, и он работает нормально, паролиCredentials заполняются. Вносили ли вы какие-либо изменения в JsonSeriliazer?
Посмотрите ответ Оливера ниже — он на 100% правильный. Не анализируйте ничего вручную — SDK уже все проанализировал. А восстановить секрет вы не сможете, это вообще невозможно по очевидным соображениям безопасности.
Десериализация List<Microsoft.Graph.Application>
не будет работать, поскольку ответом является объект JSON, а не массив JSON.
Попробуйте десериализовать ответ как GraphServiceApplicationsCollectionPage
if (response.IsSuccessStatusCode)
{
// Shows the correct data in JSON Format
var rawData = await response.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<GraphServiceApplicationsCollectionPage>(rawData);
apps = response.CurrentPage.ToList();
}
Итак, когда я это делаю, он все равно взрывается и дает тот же результат. Он никогда не доходит до вызова DeserializeObject
. Я не знал о классе GraphServiceApplicationsCollectionPage
, так что было приятно узнать о нем.
попробуйте код ниже, чтобы преобразовать результат строки.
var rawData = await response.Content.ReadAsStringAsync();
dynamic obj = JsonConvert.DeserializeObject<dynamic>(rawData);
var appList = obj.value;
List<Application> apps = JsonConvert.DeserializeObject<List<Application>>(appList.ToString());
Кстати, для получения результата я все же рекомендую использовать Graph sdk. Похоже, вы пытаетесь использовать V5 Graph SDK. Попробуйте мои коды ниже. Коды и конфигурации в Program.cs и appsettings.json одинаковы, необходимые пакеты nuget и коды GraphClient имеют некоторые различия.
<ItemGroup>
<PackageReference Include = "Microsoft.AspNetCore.Authentication.JwtBearer" Version = "8.0.0" NoWarn = "NU1605" />
<PackageReference Include = "Microsoft.AspNetCore.Authentication.OpenIdConnect" Version = "8.0.0" NoWarn = "NU1605" />
<PackageReference Include = "Microsoft.Identity.Web" Version = "2.15.2" />
<PackageReference Include = "Microsoft.Identity.Web.UI" Version = "2.15.2" />
<PackageReference Include = "Microsoft.Identity.Web.DownstreamApi" Version = "2.15.2" />
<PackageReference Include = "Microsoft.Identity.Web.GraphServiceClient" Version = "2.15.2" />
</ItemGroup>
//UserCollectionResponse
var users = await _graphServiceClient.Users.GetAsync();
//ApplicationCollectionResponse
var apps = await _graphServiceClient.Applications
.GetAsync(requestionConfiguration =>
{
requestionConfiguration.QueryParameters.Select = new string[] { "Id", "DisplayName", "passwordCredentials" };
}
);
Если вы используете поток учетных данных клиента, который использует разрешение Application Api, вы можете попробовать коды ниже.
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenantId";
var clientId = "clientId";
var clientSecret = "clientSecret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
//UserCollectionResponse
var users = await graphClient.Users.GetAsync();
//ApplicationCollectionResponse
var apps = await graphClient.Applications
.GetAsync(requestionConfiguration =>
{
requestionConfiguration.QueryParameters.Select = new string[] { "Id", "DisplayName", "passwordCredentials" };
}
);
Я думаю, что @whisk нужно решение для SDK v4.
После того, как я попробовал то же самое и изучил ваш код, кажется, что вы слишком много пытаетесь. SDK уже выполняет за вас десериализацию, и вы можете просто работать со свойствами объектов. Этот пример кода вернет все приложения:
using Azure.Core;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensibility;
namespace ConsoleApp
{
public static class Program
{
private static readonly string ClientId = "{your client id}";
private static readonly string[] Scopes = new[] { "https://graph.microsoft.com/.default" };
// Replace with your tenant id, if it is a single tenant app.
private static readonly string TenantId = "common";
private static AccessToken _cachedToken;
public static async Task Main()
{
var credential = DelegatedTokenCredential.Create((_, _)
=> throw new NotSupportedException(),
GetOrRequestAccessToken);
var graphClient = new GraphServiceClient(credential, Scopes);
var options = new[]
{
new QueryOption("$select", "id,displayName,passwordCredentials"),
};
var appsFound = await graphClient.Applications.Request(options).GetAsync();
foreach (var app in appsFound)
{
Console.WriteLine($"Id: {app.Id}");
Console.WriteLine($"Display Name: {app.DisplayName}");
foreach (var password in app.PasswordCredentials)
{
Console.WriteLine($" Key Id: {password.KeyId}");
Console.WriteLine($" Display Name: {password.DisplayName}");
Console.WriteLine($" Start: {password.StartDateTime}");
Console.WriteLine($" End: {password.EndDateTime}");
Console.WriteLine($" Hint: {password.Hint}");
}
Console.WriteLine();
}
}
private static async ValueTask<AccessToken> GetOrRequestAccessToken(TokenRequestContext _, CancellationToken __)
{
if (_cachedToken.ExpiresOn <= DateTimeOffset.UtcNow)
{
var app = PublicClientApplicationBuilder.Create(ClientId)
.WithExperimentalFeatures()
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.WithRedirectUri("http://localhost")
.Build();
var result = await app.AcquireTokenInteractive(Scopes)
.OnBeforeTokenRequest(request =>
{
request.Headers.Add("Origin", "http://localhost");
return Task.CompletedTask;
})
.ExecuteAsync();
_cachedToken = new AccessToken(result.AccessToken, result.ExpiresOn);
}
return _cachedToken;
}
}
}
Если вы хотите получить значение SecretText из Graph SDK, вы должны знать, что это невозможно согласно документации :
secretText: содержит надежные пароли, созданные Microsoft Entra ID, длиной от 16 до 64 символов. Сгенерированное значение пароля возвращается только во время первоначального запроса POST к addPassword. В будущем восстановить этот пароль невозможно.
value
— это список в данных JSON. Весь json сам по себе не является списком.