diff --git a/SpotifyAPI.Docs/docs/5_to_6.md b/SpotifyAPI.Docs/docs/5_to_6.md new file mode 100644 index 00000000..721f2e01 --- /dev/null +++ b/SpotifyAPI.Docs/docs/5_to_6.md @@ -0,0 +1,154 @@ +--- +id: 5_to_6 +title: 5.x.x to 6.x.x +--- + +## SpotifyAPI.Web + +### Initialization + +In `5.x`, a new `SpotifyWebAPI` instance could be created without supplying necessary values, since they were implemented as properties. With `6.x`, necessary values have to be given in the constructor and `SpotifyWebAPI` has been renamed to `SpotifyClient`. Also, `SpotifyClientConfig` has been introduced to give a better configuration experience, including retry handlers, automatic authenticators and proxy configurations. + +```csharp +// OLD +var spotify = new SpotifyWebAPI { AccessToken = "YourAccessToken" }; +var spotify = new SpotifyWebAPI(ProxyConfig); // No access token - invalid + +// NEW +var spotify = new SpotifyClient("YourAccessToken"); + +var config = SpotifyClientConfig + .CreateDefault() + .WithToken("YourAccessToken"); +var spotify = new SpotifyClient(config); + +var config = SpotifyClientConfig + .CreateDefault() + .WithAuthenticator(new ClientCredentialsAuthenticator(CLIENT_ID, CLIENT_SECRET)); // takes care of access tokens +var spotify = new SpotifyClient(config); +``` + +For some performance guides, have a look at the [Configuration Guide](./configuration.md) + +### Proxy + +In `5.x`, the proxy configuration could be passed to the `SpotifyWebAPI` constructor. In `6.x`, they're part of the HTTP Client. The built-in http client supports proxies out of the box: + +```csharp +var httpClient = new NetHttpClient(new ProxyConfig("localhost", 8080) +{ + User = "", + Password = "", + SkipSSLCheck = false, +}); +var config = SpotifyClientConfig + .CreateDefault() + .WithHTTPClient(httpClient); + +var spotify = new SpotifyClient(config); +``` + +### Calling API Endpoints + +In `5.x`, there was one big instance to support all API endpoints. Parameters to these endpoints were passed directly as method parameters. Optional parameters were nullable and could be excluded. In `6.x`, every endpoint group (`albums`, `tracks`, `userprofile`) has their own API-Client, which is available as a property in a `SpotifyClient` instance. While URI path parameters are still passed as method parameter, query and body parameters are now passed as a grouped class instance, where required parameters are needed in the constructor and optional parameters can be supplied as properties. All endpoints are also only implemented as async methods. + +```csharp +// OLD: +PrivateProfile profile = await spotify.GetPrivateProfileAsync(); +var playlists = await spotify.GetUserPlaylists(profile.Id, 100, 0); + +// NEW: +PrivateUser user = await spotify.UserProfile.Current(); +var playlists = await spotify.Playlists.GetUsers(user.Id, new PlaylistGetUsersRequest +{ + Limit = 100, + Offset = 0 +}); +``` + +All required arguments are checked for non-null values. If it's null, the methods will throw a `ArgumentNullException` + +### Error/Header Handling + +In `5.x`, all response models included a base error model, with properties like `Headers`, `Error` and `HasError`. This was not a good decision since response models should be clean and only contain API response data. In `6.x`, error handling is `Exception` based. For example, if the access token is invalid, calling API endpoints will throw a `APIUnauthorizedException`. If you hit the API too many times, the method will throw a `APITooManyRequestsException`. They all derive from a base exception `APIException`, which is also thrown in more general cases, e.g bad request input parameters. If you're interested in the headers of the last response, you can use `spotify.LastResponse`, **make sure there is only one thread using this instance!** + +```csharp +// OLD: +PrivateProfile profile = await spotify.GetPrivateProfileAsync(); +if(profile.HasError()) +{ + // handle error +} +var headers = profile.Headers(); // access to headers + +// NEW: +try +{ + PrivateProfile profile = await spotify.GetPrivateProfileAsync(); + var response = spotify.LastResponse; // response.Headers +} +catch (APIUnauthorizedException e) +{ + // handle unauthorized error + // e.Response contains HTTP response + // e.Message contains Spotify error message +} +catch (APIException e) +{ + // handle common error + // e.Response contains HTTP response + // e.Message contains Spotify error message +} +``` + +More Info: [Error Handling](./error_handling) + +## SpotifyAPI.Web.Auth + +In `5.x`, `SpotifyAPI.Web.Auth` contained every logic related to the OAuth flows. In `6.x`, `SpotifyAPI.Web.Auth` is only required if you need a HTTP Server for handling OAuth responses. For example, if you're in a ASP.NET environment or just use the [Client Credentials](client_credentials) flow, there is no need to install `SpotifyAPI.Web.Auth` anymore. + +### Authorization Code Auth + +As an example, this shows how to convert a `5.x` authorization code flow to `6.x`: + +```csharp +// OLD +var auth = + new AuthorizationCodeAuth(_clientId, _secretId, "http://localhost:4002", "http://localhost:4002", + Scope.PlaylistReadPrivate | Scope.PlaylistReadCollaborative); +auth.AuthReceived += AuthOnAuthReceived; +auth.Start(); +auth.OpenBrowser(); + +private static async void AuthOnAuthReceived(object sender, AuthorizationCode payload) +{ + var auth = (AuthorizationCodeAuth) sender; + auth.Stop(); + + Token token = await auth.ExchangeCode(payload.Code); + var spotify = new SpotifyWebAPI { AccessToken = token.AccessToken }; + await PrintUsefulData(spotify); +} + +// NEW +var config = SpotifyClientConfig.CreateDefault(); +var server = new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000); +server.AuthorizationCodeReceived += async (sender, response) => +{ + await server.Stop(); + var tokenResponse = await new OAuthClient(config).RequestToken(new AuthorizationCodeTokenRequest( + _clientId, _secretId, response.Code, server.BaseUri + )); + + var spotify = new SpotifyClient(config.WithToken(tokenResponse.AccessToken)); +} +await server.Start(); + +var loginRequest = new LoginRequest(server.BaseUri, _clientId, LoginRequest.ResponseType.Code) +{ + Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative } +}; +BrowserUtil.Open(loginRequest.ToUri()); +``` + +While it is more code to write, there is a better seperation of concerns. For example, it is able to construct a `LoginRequest` without starting a server. This `LoginRequest` can also be used to forward the user to in a web-based context. The same auth server `EmbedIOAuthServer` can be used to receive `AuthorizationCodes` and `ImplictGrants` responses. diff --git a/SpotifyAPI.Docs/sidebars.js b/SpotifyAPI.Docs/sidebars.js index 2341d2fd..69b673d8 100644 --- a/SpotifyAPI.Docs/sidebars.js +++ b/SpotifyAPI.Docs/sidebars.js @@ -14,8 +14,8 @@ module.exports = { 'pagination', 'retry_handling', 'iplayableitem', - 'unit_testing' - ] + 'unit_testing', + ], }, { type: 'category', @@ -26,8 +26,8 @@ module.exports = { 'implicit_grant', 'authorization_code', 'pkce', - 'token_swap' - ] + 'token_swap', + ], }, 'showcase', { @@ -40,9 +40,14 @@ module.exports = { 'example_cli_custom_html', 'example_cli_persistent_config', 'example_token_swap', - 'example_uwp' - ] + 'example_uwp', + ], }, - ] - } + { + type: 'category', + label: 'Migration Guides', + items: ['5_to_6'], + }, + ], + }, }; diff --git a/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Example.CLI.PersistentConfig.csproj b/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Example.CLI.PersistentConfig.csproj index 608c17db..1ea7eb3f 100644 --- a/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Example.CLI.PersistentConfig.csproj +++ b/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig/Example.CLI.PersistentConfig.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 8.0 enable