anastasijasavov 3 роки тому
джерело
коміт
37929983d0
30 змінених файлів з 598 додано та 30 видалено
  1. 16
    0
      GrpcShared/DTO/TokenMessage.cs
  2. 18
    0
      GrpcShared/DTO/Track/MultipleTrack/MultipleTrackRequest.cs
  3. 74
    0
      GrpcShared/DTO/Track/MultipleTrack/MultipleTrackResponse.cs
  4. 18
    0
      GrpcShared/DTO/Track/SaveTracks/SaveTracksRequest.cs
  5. 18
    0
      GrpcShared/DTO/Track/SingleTrack/SingleTrackRequest.cs
  6. 67
    0
      GrpcShared/DTO/Track/SingleTrack/SingleTrackResponse.cs
  7. 20
    0
      GrpcShared/DTO/User/UserInfoResponse.cs
  8. 9
    0
      GrpcShared/GLOBALS.cs
  9. 2
    0
      GrpcShared/GrpcShared.csproj
  10. 4
    1
      GrpcShared/Interfaces/IAuthService.cs
  11. 6
    0
      GrpcShared/Interfaces/ITrackService.cs
  12. 1
    0
      IdentityProvider/IdentityProvider.csproj
  13. 2
    0
      IdentityProvider/Program.cs
  14. 32
    0
      IdentityProvider/Services/AuthService.cs
  15. 1
    1
      IdentityProvider/appsettings.json
  16. 5
    3
      NemAnCore/App.razor
  17. 38
    3
      NemAnCore/Pages/FetchData.razor
  18. 26
    1
      NemAnCore/Pages/Index.razor
  19. 3
    0
      NemAnCore/Program.cs
  20. 41
    3
      NemAnCore/Services/AuthClientService.cs
  21. 3
    0
      NemAnCore/Services/Interfaces/IAuthClientService.cs
  22. 6
    0
      NemAnCore/Services/Interfaces/ITrackClientService.cs
  23. 18
    0
      NemAnCore/Services/TrackClientService.cs
  24. 1
    1
      NemAnCore/Shared/MainLayout.razor
  25. 7
    7
      NemAnCore/Shared/NavMenu.razor
  26. 1
    1
      NemAnCore/_Imports.razor
  27. 1
    1
      NemAnCore/wwwroot/index.html
  28. 56
    4
      gRPCServer/Program.cs
  29. 101
    4
      gRPCServer/Services/TrackService.cs
  30. 3
    0
      gRPCServer/SpotifyService.csproj

+ 16
- 0
GrpcShared/DTO/TokenMessage.cs Переглянути файл

@@ -0,0 +1,16 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO
{
[ProtoContract]
public class TokenMessage
{
[ProtoMember(1)]
public string? Token { get; set; }
}
}

+ 18
- 0
GrpcShared/DTO/Track/MultipleTrack/MultipleTrackRequest.cs Переглянути файл

@@ -0,0 +1,18 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.Track.MultipleTrack
{
[ProtoContract]
public class MultipleTrackRequest
{
[ProtoMember(1)]
public List<string> Ids { get; set; }
[ProtoMember(2)]
public string Token { get; set; }
}
}

+ 74
- 0
GrpcShared/DTO/Track/MultipleTrack/MultipleTrackResponse.cs Переглянути файл

@@ -0,0 +1,74 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.Track.MultipleTrack
{
[ProtoContract]
public class MultipleTrackResponse
{
[ProtoMember(1)]
public List<AudioFeature>? Audio_Features { get; set; }
}
[ProtoContract]
public class AudioFeature
{
[ProtoMember(1)]
public double Danceability { get; set; }

[ProtoMember(2)]
public double Energy { get; set; }

[ProtoMember(3)]
public long Key { get; set; }

[ProtoMember(4)]
public double Loudness { get; set; }

[ProtoMember(5)]
public long Mode { get; set; }

[ProtoMember(6)]
public double Speechiness { get; set; }

[ProtoMember(7)]
public double Acousticness { get; set; }

[ProtoMember(8)]
public long Instrumentalness { get; set; }

[ProtoMember(9)]
public double Liveness { get; set; }

[ProtoMember(10)]
public double Valence { get; set; }

[ProtoMember(11)]
public double Tempo { get; set; }

[ProtoMember(12)]
public string Type { get; set; }

[ProtoMember(13)]
public string Id { get; set; }

[ProtoMember(14)]
public string Uri { get; set; }

[ProtoMember(15)]
public Uri TrackHref { get; set; }

[ProtoMember(16)]
public Uri AnalysisUrl { get; set; }

[ProtoMember(17)]
public long DurationMs { get; set; }

[ProtoMember(18)]
public long TimeSignature { get; set; }
}
}

+ 18
- 0
GrpcShared/DTO/Track/SaveTracks/SaveTracksRequest.cs Переглянути файл

@@ -0,0 +1,18 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.Track.SaveTracks
{
[ProtoContract]
public class SaveTracksRequest
{
[ProtoMember(1)]
public List<string> Ids { get; set; }
[ProtoMember(2)]
public string Token { get; set; }
}
}

+ 18
- 0
GrpcShared/DTO/Track/SingleTrack/SingleTrackRequest.cs Переглянути файл

@@ -0,0 +1,18 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.Track.SingleTrack
{
[ProtoContract]
public class SingleTrackRequest
{
[ProtoMember(1)]
public string Id { get; set; }
[ProtoMember(2)]
public string Token { get; set; }
}
}

+ 67
- 0
GrpcShared/DTO/Track/SingleTrack/SingleTrackResponse.cs Переглянути файл

@@ -0,0 +1,67 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.Track.SingleTrack
{
[ProtoContract]
public class SingleTrackResponse
{
[ProtoMember(1)]
public double Danceability { get; set; }

[ProtoMember(2)]
public double Energy { get; set; }

[ProtoMember(3)]
public long Key { get; set; }

[ProtoMember(4)]
public double Loudness { get; set; }

[ProtoMember(5)]
public long Mode { get; set; }

[ProtoMember(6)]
public double Speechiness { get; set; }

[ProtoMember(7)]
public double Acousticness { get; set; }

[ProtoMember(8)]
public long Instrumentalness { get; set; }

[ProtoMember(9)]
public double Liveness { get; set; }

[ProtoMember(10)]
public double Valence { get; set; }

[ProtoMember(11)]
public double Tempo { get; set; }

[ProtoMember(12)]
public string Type { get; set; }

[ProtoMember(13)]
public string Id { get; set; }

[ProtoMember(14)]
public string Uri { get; set; }

[ProtoMember(15)]
public Uri TrackHref { get; set; }

[ProtoMember(16)]
public Uri AnalysisUrl { get; set; }

[ProtoMember(17)]
public long DurationMs { get; set; }

[ProtoMember(18)]
public long TimeSignature { get; set; }
}
}

+ 20
- 0
GrpcShared/DTO/User/UserInfoResponse.cs Переглянути файл

@@ -0,0 +1,20 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.User
{
[ProtoContract]
public class UserInfoResponse
{
[ProtoMember(1)]
public string? email { get; set; }
[ProtoMember(2)]
public string? id { get; set; }
[ProtoMember(3)]
public string? display_name { get; set; }
}
}

+ 9
- 0
GrpcShared/GLOBALS.cs Переглянути файл

@@ -0,0 +1,9 @@
using System;
namespace Shared
{
public static class GLOBALS
{
public const String SPOTIFYURL = "https://api.spotify.com/v1/";
public const String MEDIATYPE = "application/json";
}
}

+ 2
- 0
GrpcShared/GrpcShared.csproj Переглянути файл

@@ -8,7 +8,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="protobuf-net.BuildTools" Version="3.1.17">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

+ 4
- 1
GrpcShared/Interfaces/IAuthService.cs Переглянути файл

@@ -1,4 +1,6 @@
using GrpcShared.DTO.Auth;
using GrpcShared.DTO;
using GrpcShared.DTO.Auth;
using GrpcShared.DTO.User;
using ProtoBuf.Grpc.Configuration;

namespace GrpcShared.Interfaces
@@ -8,6 +10,7 @@ namespace GrpcShared.Interfaces
{
Task<TokenResponse> GetAccessToken(TokenRequest code);
Task<CodeRequest> GetAuthParams();
Task<UserInfoResponse> GetUserInfo(TokenMessage token);
//Task<ClientSecrets> GetClientSecrets();
}
}

+ 6
- 0
GrpcShared/Interfaces/ITrackService.cs Переглянути файл

@@ -1,4 +1,7 @@
using GrpcShared.DTO.Search;
using GrpcShared.DTO.Track.MultipleTrack;
using GrpcShared.DTO.Track.SaveTracks;
using GrpcShared.DTO.Track.SingleTrack;
using ProtoBuf.Grpc.Configuration;
using System;
using System.Collections.Generic;
@@ -12,5 +15,8 @@ namespace GrpcShared.Interfaces
public interface ITrackService
{
Task<SearchResponse> ListSearchAsync(SearchRequest request);
Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request);
Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request);
Task SaveTracks(SaveTracksRequest request);
}
}

+ 1
- 0
IdentityProvider/IdentityProvider.csproj Переглянути файл

@@ -15,6 +15,7 @@
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.22.0" />
</ItemGroup>

<ItemGroup>

+ 2
- 0
IdentityProvider/Program.cs Переглянути файл

@@ -5,6 +5,7 @@ using ProtoBuf.Grpc.Server;
using Microsoft.Extensions.Options;
using GrpcShared.DTO.Auth;
using SpotifyService.Services;
using Blazored.SessionStorage;

var builder = WebApplication.CreateBuilder(args);
#if DEBUG
@@ -36,6 +37,7 @@ builder.Services.AddEndpointsApiExplorer();
builder.Services.AddGrpc();
builder.Services.AddCodeFirstGrpc();
builder.Services.AddCodeFirstGrpcReflection();
builder.Services.AddBlazoredSessionStorage();
//call spotify api
builder.Services.AddHttpClient();


+ 32
- 0
IdentityProvider/Services/AuthService.cs Переглянути файл

@@ -1,11 +1,17 @@
//using IdentityProvider.Protos.AuthService;
using Blazored.SessionStorage;
using Grpc.Net.Client;
using GrpcShared;
using GrpcShared.DTO;
using GrpcShared.DTO.Auth;
using GrpcShared.DTO.User;
using GrpcShared.Interfaces;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
@@ -17,6 +23,7 @@ namespace IdentityProvider.Services
private readonly ILogger<AuthService> _logger;
private readonly CodeRequest _params;
private readonly IHttpClientFactory _httpClientFactory;
//private ISessionStorageService _sessionStorageService;
public AuthService(ILogger<AuthService> logger, IOptions<CodeRequest> options, IHttpClientFactory httpClientFactory)
{
_logger = logger;
@@ -74,5 +81,30 @@ namespace IdentityProvider.Services
return await Task.FromResult(authParams);
}

public async Task<UserInfoResponse> GetUserInfo(TokenMessage tokenM)
{
//var des = JsonConvert.DeserializeObject<TokenMessage>(tokenM);
//var tokenStorage = _sessionStorageService.GetItemAsync<string>("token");

//hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
//var response = hc.GetAsync(userInfoUrl).Result;
//dynamic userInfo = response.Content.ReadAsAsync().Result;
//return userInfo;

var http = _httpClientFactory.CreateClient();
http.BaseAddress = new Uri("https://api.spotify.com/v1/me");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenM.Token);
var response = http.GetAsync(http.BaseAddress).Result;
var userInfo = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync())!;
return userInfo;

//http.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token.Token);

//var response = await http.GetAsync(http.BaseAddress + "me");

//var user = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync());

//return await Task.FromResult(user!);
}
}
}

+ 1
- 1
IdentityProvider/appsettings.json Переглянути файл

@@ -14,7 +14,7 @@
"AuthParams": {
"ClientId": "83e1d09876b049c4bb1953185a4b3bfb",
"RedirectURI": "https://localhost:44342/callback",
"Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read",
"Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read user-read-private",
"ClientSecret": "ea752433d0774fad87fab5c1ee788c8d"
}
}

+ 5
- 3
NemAnCore/App.razor Переглянути файл

@@ -1,8 +1,10 @@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" >
<NotAuthorized>Sorry nisi autorizovan</NotAuthorized>
<Authorizing>
<text>Please wait, we are authorizint the user.</text>
</Authorizing>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
@@ -13,4 +15,4 @@
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</CascadingAuthenticationState>

+ 38
- 3
NemAnCore/Pages/FetchData.razor Переглянути файл

@@ -1,23 +1,58 @@
@page "/search"
@attribute [Authorize]
@using Grpc.Core
@using GrpcShared.DTO
@using GrpcShared.DTO.Search
@using GrpcShared.DTO.Track.MultipleTrack
@using GrpcShared.DTO.Track.SingleTrack
@using Microsoft.AspNetCore.Authorization
@using NemAnBlazor.Services
@using NemAnBlazor.Services.Interfaces
@using System.Diagnostics
@*@inject HttpClient Http*@
@inject Blazored.SessionStorage.ISessionStorageService sessionStorage
@inject ITrackClientService SearchService
@inject IAuthClientService AuthService


<AuthorizeView>
<Authorized>
The user is authorized
</Authorized>
<NotAuthorized>
The User is not authorized
</NotAuthorized>
</AuthorizeView>

<PageTitle>Search</PageTitle>

<h1>Search</h1>

<AuthorizeView>
<Authorized>
<button class="btn btn-primary" @onclick="Click">Click me</button>
</Authorized>
</AuthorizeView>



@code {
protected override async Task OnInitializedAsync()
{
var token = await sessionStorage.GetItemAsync<string>("token");
//MultipleTrackRequest mreq = new() { Ids = new List<string>(){"3JAeYOjyJodI4PRs44lx2l", "6clZa1yrZe7pJrYFUcD9KW"}, Token = token };
//MultipleTrackResponse multipleTrackResponse = await SearchService.GetListMultipleTrackAsync(mreq);

SearchRequest request = new() { Query = "aitch", Type = "track", Token = token };
SearchResponse searchResponse = await SearchService.GetListSearchAsync(request);
//SingleTrackRequest singleTrackRequest = new() { Id = "3JAeYOjyJodI4PRs44lx2l", Token = token };
//SingleTrackResponse singleTrackResponse = await SearchService.GetListSingleTrackAsync(singleTrackRequest);
}

private async Task Click(){

var token = await sessionStorage.GetItemAsync<string>("token");
TokenMessage tm = new() { Token = token };
SearchRequest request = new() { Query = "aitch", Type = "track", Token = token };
SearchResponse searchResponse = await SearchService.GetListSearchAsync(request);
}



+ 26
- 1
NemAnCore/Pages/Index.razor Переглянути файл

@@ -8,22 +8,47 @@
@inject NavigationManager NavigationManager
@inject IAuthClientService AuthService
@inject ITrackClientService SearchService
@using System.Security.Claims




<AuthorizeView>
<Authorized>
Dobrodosli @context.User.Claims.FirstOrDefault(x => x.Type == "name")?.Value.ToUpper()
</Authorized>
<NotAuthorized>
Nisi autorizovan.
<button class="btn btn-primary" @onclick="Login">Autorizuj</button>
</NotAuthorized>
</AuthorizeView>

<PageTitle>Index</PageTitle>

<h1>Pozdrav Diligent!</h1>


Dobrodošli u našu NemAn aplikaciju.




@code {
private string message;

protected override async Task OnInitializedAsync()
{
message = "Cao";
}

private async Task Login()
{
//var response = await SearchService.GetListSearchAsync(new GrpcShared.DTO.Search.SearchRequest() { Query="venom", Type = "track"});
CodeRequest authParams = await AuthService.GetAuthParams();
CodeRequest authParams = await AuthService.GetAuthParams();
// await AuthService.GetAccessToken(new CodeResponse{ Code = "hello"});
string url = $"https://accounts.spotify.com/en/authorize?client_id={authParams.ClientId}&redirect_uri={authParams.RedirectURI}&response_type={authParams.ResponseType}&scope={authParams.Scope}&show_dialog={authParams.ShowDialog}";

NavigationManager.NavigateTo(url);

}
}

+ 3
- 0
NemAnCore/Program.cs Переглянути файл

@@ -3,6 +3,7 @@ using Blazored.SessionStorage;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using NemAnBlazor;
@@ -22,6 +23,8 @@ builder.Services.AddScoped(_ =>

return channel;
});
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, AuthClientService>();

builder.Services.AddScoped<ITrackClientService, TrackClientService>();
builder.Services.AddScoped<IAuthClientService, AuthClientService>();

+ 41
- 3
NemAnCore/Services/AuthClientService.cs Переглянути файл

@@ -4,16 +4,23 @@ using GrpcShared.Interfaces;
using NemAnBlazor.Services.Interfaces;
using ProtoBuf.Grpc.Client;
using GrpcShared;
using GrpcShared.DTO.User;
using GrpcShared.DTO;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Blazored.SessionStorage;

namespace NemAnBlazor.Services
{
public class AuthClientService : IAuthClientService
public class AuthClientService : AuthenticationStateProvider, IAuthClientService
{

private IAuthService _serviceClient;
public AuthClientService(GrpcChannel grpcChannel)
private ISessionStorageService _sessionStorage;
public AuthClientService(GrpcChannel grpcChannel, ISessionStorageService sessionStorage)
{
_serviceClient = grpcChannel.CreateGrpcService<IAuthService>();
_sessionStorage = sessionStorage;
}
public async Task<TokenResponse> GetAccessToken(TokenRequest request)
{
@@ -24,6 +31,37 @@ namespace NemAnBlazor.Services
{
return await _serviceClient.GetAuthParams();
}

public async Task<UserInfoResponse> GetUserInfo(TokenMessage token)
{
return await _serviceClient.GetUserInfo(token);
}

public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
await Task.Delay(1500);

string token = await _sessionStorage.GetItemAsync<string>("token");

//token = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN";
if (token == null) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));

var userInfo = await _serviceClient.GetUserInfo(new TokenMessage { Token = token });

List<Claim> claims = new();

claims.Add(new Claim("email", userInfo.email!));
claims.Add(new Claim("id", userInfo.id!));
claims.Add(new Claim("name", userInfo.display_name!));

ClaimsIdentity identity = new(claims, "jwt");
//ClaimsIdentity identity = new();
ClaimsPrincipal user = new(identity);
AuthenticationState state = new(user);

NotifyAuthenticationStateChanged(Task.FromResult(state));

return state;
}
}
}

+ 3
- 0
NemAnCore/Services/Interfaces/IAuthClientService.cs Переглянути файл

@@ -1,5 +1,7 @@
using GrpcShared;
using GrpcShared.DTO;
using GrpcShared.DTO.Auth;
using GrpcShared.DTO.User;

namespace NemAnBlazor.Services.Interfaces
{
@@ -7,5 +9,6 @@ namespace NemAnBlazor.Services.Interfaces
{
Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest);
Task<CodeRequest> GetAuthParams();
Task<UserInfoResponse> GetUserInfo(TokenMessage token);
}
}

+ 6
- 0
NemAnCore/Services/Interfaces/ITrackClientService.cs Переглянути файл

@@ -1,9 +1,15 @@
using GrpcShared.DTO.Search;
using GrpcShared.DTO.Track.MultipleTrack;
using GrpcShared.DTO.Track.SaveTracks;
using GrpcShared.DTO.Track.SingleTrack;

namespace NemAnBlazor.Services.Interfaces
{
public interface ITrackClientService
{
Task<SearchResponse> GetListSearchAsync(SearchRequest request);
Task<SingleTrackResponse> GetListSingleTrackAsync(SingleTrackRequest request);
Task<MultipleTrackResponse> GetListMultipleTrackAsync(MultipleTrackRequest request);
Task PutSaveTracks(SaveTracksRequest request);
}
}

+ 18
- 0
NemAnCore/Services/TrackClientService.cs Переглянути файл

@@ -1,5 +1,8 @@
using Grpc.Net.Client;
using GrpcShared.DTO.Search;
using GrpcShared.DTO.Track.MultipleTrack;
using GrpcShared.DTO.Track.SaveTracks;
using GrpcShared.DTO.Track.SingleTrack;
using GrpcShared.Interfaces;
using NemAnBlazor.Services.Interfaces;
using ProtoBuf.Grpc.Client;
@@ -15,9 +18,24 @@ namespace NemAnBlazor.Services
_serviceClient = grpcChannel.CreateGrpcService<ITrackService>();
}


public async Task<SearchResponse> GetListSearchAsync(SearchRequest request)
{
return await _serviceClient.ListSearchAsync(request);
}

public async Task<SingleTrackResponse> GetListSingleTrackAsync(SingleTrackRequest request)
{
return await _serviceClient.ListSingleTrackAsync(request);
}
public async Task<MultipleTrackResponse> GetListMultipleTrackAsync(MultipleTrackRequest request)
{
return await _serviceClient.ListMultipleTrackAsync(request);
}

public async Task PutSaveTracks(SaveTracksRequest request)
{
await _serviceClient.SaveTracks(request);
}
}
}

+ 1
- 1
NemAnCore/Shared/MainLayout.razor Переглянути файл

@@ -1,7 +1,7 @@
@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<div style="background: green ;" class="sidebar">
<NavMenu />
</div>


+ 7
- 7
NemAnCore/Shared/NavMenu.razor Переглянути файл

@@ -1,27 +1,27 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">NemAnCore</a>
<a class="navbar-brand" href="">Spotify</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu" >
<nav class="flex-column" >
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
@* <div class="nav-item px-3">
<NavLink class="nav-link" href="callback">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
</div>*@
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
<NavLink class="nav-link" href="search">
<span class="oi oi-list-rich" aria-hidden="true"></span> Search
</NavLink>
</div>
</nav>

+ 1
- 1
NemAnCore/_Imports.razor Переглянути файл

@@ -9,4 +9,4 @@
@using NemAnBlazor
@using NemAnBlazor.Shared
@using System.Web
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Authorization

+ 1
- 1
NemAnCore/wwwroot/index.html Переглянути файл

@@ -8,7 +8,7 @@
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="NemAnCore.styles.css" rel="stylesheet" />
<link href="NemAnBlazor.styles.css" rel="stylesheet" />
</head>

<body>

+ 56
- 4
gRPCServer/Program.cs Переглянути файл

@@ -1,7 +1,11 @@
using Microsoft.AspNetCore.Server.Kestrel.Core;
using NemAnBlazor.Services.Interfaces;
using NemAnBlazor.Services;
using ProtoBuf.Grpc.Server;
using SpotifyService.Services;

using GrpcShared.Interfaces;
using Polly;
using Polly.Extensions.Http;

var builder = WebApplication.CreateBuilder(args);

@@ -23,7 +27,54 @@ builder.Services.AddHttpClient("HttpClient", c =>
{
c.BaseAddress = new Uri(SpotifyService.GLOBALS.SPOTIFYURL);
c.DefaultRequestHeaders.Add("Accept", SpotifyService.GLOBALS.MEDIATYPE);
});
})
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreaker())
.AddPolicyHandler(GetBulkheadPolicy(50,200));

IAsyncPolicy<HttpResponseMessage> GetBulkheadPolicy(int capacity, int queueLength)
{
//As soon as we hit 50 concurrent requests, the policy will add subsequent requests to the pending request queue.
//Once the pending request queue is full, then the policy will start rejecting any other calls to the service.
return Policy.BulkheadAsync<HttpResponseMessage>(capacity, queueLength);
}

IAsyncPolicy<HttpResponseMessage> GetCircuitBreaker()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
//the circuit will be cut if 25% of requests fail in a 60 second window, with a minimum of 7 requests in the 60 second window,
//then the circuit should be cut for 30 seconds:
.AdvancedCircuitBreakerAsync(0.25, TimeSpan.FromSeconds(60),
7, TimeSpan.FromSeconds(30), OnBreak, OnReset, OnHalfOpen);
}

void OnHalfOpen()
{
Console.WriteLine("Circuit in test mode, one request will be allowed.");
}

void OnReset()
{
Console.WriteLine("Circuit closed, requests flow normally.");
}

void OnBreak(DelegateResult<HttpResponseMessage> result, TimeSpan ts)
{
Console.WriteLine("Circuit cut, requests will not flow.");
}

IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
// HttpRequestException, 5XX and 408
.HandleTransientHttpError()
// 404
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
// Retry two times after delay
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)));
}

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
@@ -64,8 +115,9 @@ app.UseGrpcWeb();
app.MapRazorPages();
app.MapControllers();

//app.MapGrpcService<WeatherService>();
//app.MapGrpcService<SearchService>().EnableGrpcWeb();
//app.MapGrpcService<AuthService>();
//app.MapGrpcService<AuthService>().EnableGrpcWeb();


app.MapCodeFirstGrpcReflectionService();


+ 101
- 4
gRPCServer/Services/TrackService.cs Переглянути файл

@@ -1,26 +1,32 @@
using Grpc.Core;
using Google.Rpc;
using Grpc.Core;
using GrpcShared;
using GrpcShared.DTO.Search;
using GrpcShared.DTO.Track.MultipleTrack;
using GrpcShared.DTO.Track.SaveTracks;
using GrpcShared.DTO.Track.SingleTrack;
using GrpcShared.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Net.Http.Headers;
using NemAnBlazor.Services;
using Newtonsoft.Json;
using System.Text;
using System.Text.Json;
using System.Web;
using static GrpcShared.DTO.Search.SearchDetails;

namespace SpotifyService.Services
{
public class TrackService : ITrackService
{
private readonly IHttpClientFactory _httpClientFactory;


public TrackService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

public async Task<SearchResponse> ListSearchAsync(SearchRequest request)
{

var client = _httpClientFactory.CreateClient("HttpClient");

client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token );
@@ -36,6 +42,97 @@ namespace SpotifyService.Services


}

public async Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request)
{
var client = _httpClientFactory.CreateClient("HttpClient");

client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token);

var trackResult = await client.GetAsync($"audio-features/{request.Id}");

var responses = JsonConvert.DeserializeObject<SingleTrackResponse>(await trackResult.Content.ReadAsStringAsync())!;

return new SingleTrackResponse
{
Id = responses!.Id,
Danceability = responses!.Danceability,
Key = responses!.Key,
Loudness = responses!.Loudness,
Mode = responses!.Mode,
Speechiness = responses!.Speechiness,
Acousticness = responses!.Acousticness,
Instrumentalness = responses!.Instrumentalness,
Liveness = responses!.Liveness,
Valence = responses!.Valence,
Tempo = responses!.Tempo,
Type = responses!.Type,
Uri = responses!.Uri,
TrackHref = responses!.TrackHref,
AnalysisUrl = responses!.AnalysisUrl,
DurationMs = responses!.DurationMs,
TimeSignature = responses!.TimeSignature,
};
}
public async Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request)
{
var client = _httpClientFactory.CreateClient("HttpClient");

client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token);

var param = new Dictionary<string, List<string>>();
param["ids"] = request.Ids;

var query = UriUtil(param);

var trackMultipleResult = await client.GetAsync($"audio-features{query}");

var responses = JsonConvert.DeserializeObject<MultipleTrackResponse>(await trackMultipleResult.Content.ReadAsStringAsync())!;

return new MultipleTrackResponse
{
Audio_Features = responses!.Audio_Features
};
}

public async Task SaveTracks(SaveTracksRequest request)
{
var client = _httpClientFactory.CreateClient("HttpClient");

client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token);

var param = new Dictionary<string, List<string>>();
param["ids"] = request.Ids;

var query = UriUtil(param);


await client.PutAsync($"me/tracks/{query}", null);


}

public static string UriUtil(Dictionary<string, List<string>> param)
{
bool startingQuestionMarkAdded = false;
var sb = new StringBuilder();

foreach (var id in param)
{
sb.Append(startingQuestionMarkAdded ? '&' : '?');
sb.Append(id.Key);
sb.Append('=');

foreach (var Xid in id.Value)
{
sb.Append(Xid);
sb.Append("%2C");
startingQuestionMarkAdded = true;
}
}
sb.Length = sb.Length - 3;
return sb.ToString();
}
}
}


+ 3
- 0
gRPCServer/SpotifyService.csproj Переглянути файл

@@ -7,10 +7,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Api.CommonProtos" Version="2.6.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
<PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.8" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="protobuf-net.Grpc" Version="1.0.171" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" />

Завантаження…
Відмінити
Зберегти