RAPID API ERROR: EXPECTED BEGIN_ARRAY BUT WAS BEGIN_OBJECT AT LINE 1 COLUMN 2 PATH $ BY USING RETROFIT - retrofit

I am using Covid Tracker Rapid API for a recipe app project. The problem occurs when trying to making GET requests to the Rapid API. The error shows expected begin array but was begin object at line 1 column 2 path $
This is my Retrofit API Class
public interface Api {
String BASE_URL = "https://who-covid-19-data.p.rapidapi.com/api/data/";
#GET("names")
Call < List < Model > > modelData( #Header("X-RapidAPI-Host") String api,
#Header("X-RapidAPI-Key") String apiKey );
}
This is my model class
public class Model {
String names;
public Model ( String names ) {
this.names = names;
}
public String getNames () {
return names;
}
public void setNames ( String names ) {
this.names = names;
}
}
This is my main activity class
public class MainActivity extends AppCompatActivity {
TextView textView;
#Override
protected void onCreate ( Bundle savedInstanceState ) {
super.onCreate ( savedInstanceState );
setContentView ( R.layout.activity_main );
textView=findViewById ( R.id.txtHell );
loadData();
}
private void loadData () {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory( GsonConverterFactory.create())
.addConverterFactory ( ScalarsConverterFactory.create () )
.build();
Api api = retrofit.create(Api.class);
Call < List < Model > > call=api.modelData ("","");
call.enqueue ( new Callback < List < Model > > ( ) {
#Override
public void onResponse ( Call < List < Model > > call , Response < List < Model > > response ) {
if(!response.isSuccessful ()){
textView.setText ( "Code"+response.code () );
}else{
List<Model> models=response.body ();
for(Model model:models){
String data="";
data +="Names :"+ model.getNames ();
textView.append (data );
}
}
}
#Override
public void onFailure ( Call < List < Model > > call , Throwable t ) {
textView.setText ( t.getMessage () );
}
} );
}
}
please help me

Everything is good with API and RapidAPI. I believe you just need to change the interface like this.
#GET("names")
Call <ListModel> modelData( #Header("X-RapidAPI-Host") String api,
#Header("X-RapidAPI-Key") String apiKey );

Related

Extending existing ABP controllers

I am using version 3.3.2 of the ABP Framework. How can I add new methods to an existing controller? I want to extend the IdentityUserController. Following the docs I am creating my own implementation as following:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IdentityUserController))]
public class MyIdentityUserController : IdentityUserController
{
public MyIdentityUserController(IIdentityUserAppService userAppService) : base(userAppService)
{
}
public override Task<PagedResultDto<IdentityUserDto>> GetListAsync(GetIdentityUsersInput input)
{
return base.GetListAsync(input);
}
[HttpGet]
[Route("my-method")]
public Task<string> MyMethod()
{
return Task.FromResult("Works");
}
}
The overrides actually work but my custom method is not visible in Swagger and when I try to access it with Postman it is not accessible either. Any ideas how I can extend existing controllers? I don't want to create a whole new controller since I have a combination with overrides and new methods. I would like to keep everything together.
First, set IncludeSelf = true — we will use this to determine whether to replace the existing controller with the extended controller, and ASP.NET Core will resolve your controller by class.
Optionally, add [ControllerName("User")] from IdentityUserController since it is not inherited:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IdentityUserController), IncludeSelf = true)]
[ControllerName("User")]
public class MyIdentityUserController : IdentityUserController
Option 1
Subclass AbpServiceConvention and override RemoveDuplicateControllers to remove the existing controller(s) instead of your extended controller:
var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
if (exposeServicesAttr.IncludeSelf)
{
var existingControllerModels = application.Controllers
.Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType))
.ToArray();
derivedControllerModels.AddRange(existingControllerModels);
Logger.LogInformation($"Removing the controller{(existingControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(existingControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}");
continue;
}
Full code of subclass:
public class MyAbpServiceConvention : AbpServiceConvention
{
public MyAbpServiceConvention(
IOptions<AbpAspNetCoreMvcOptions> options,
IConventionalRouteBuilder conventionalRouteBuilder)
: base(options, conventionalRouteBuilder)
{
}
protected override void RemoveDuplicateControllers(ApplicationModel application)
{
var derivedControllerModels = new List<ControllerModel>();
foreach (var controllerModel in application.Controllers)
{
if (!controllerModel.ControllerType.IsDefined(typeof(ExposeServicesAttribute), false))
{
continue;
}
if (Options.IgnoredControllersOnModelExclusion.Contains(controllerModel.ControllerType))
{
continue;
}
var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
if (exposeServicesAttr.IncludeSelf)
{
var existingControllerModels = application.Controllers
.Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType))
.ToArray();
derivedControllerModels.AddRange(existingControllerModels);
Logger.LogInformation($"Removing the controller{(existingControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(existingControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}");
continue;
}
var baseControllerTypes = controllerModel.ControllerType
.GetBaseClasses(typeof(Controller), includeObject: false)
.Where(t => !t.IsAbstract)
.ToArray();
if (baseControllerTypes.Length > 0)
{
derivedControllerModels.Add(controllerModel);
Logger.LogInformation($"Removing the controller {controllerModel.ControllerType.AssemblyQualifiedName} from the application model since it replaces the controller(s): {baseControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")}");
}
}
application.Controllers.RemoveAll(derivedControllerModels);
}
}
Option 2
Implement IApplicationModelConvention to add your extended controller to IgnoredControllersOnModelExclusion and remove the existing controller:
public class ExtendedControllerApplicationModelConvention : IApplicationModelConvention
{
private readonly Lazy<IOptions<AbpAspNetCoreMvcOptions>> _lazyOptions;
public ExtendedControllerApplicationModelConvention (IServiceCollection services)
{
_lazyOptions = services.GetRequiredServiceLazy<IOptions<AbpAspNetCoreMvcOptions>>();
}
public void Apply(ApplicationModel application)
{
var controllerModelsToRemove = new List<ControllerModel>();
var ignoredControllersOnModelExclusion = _lazyOptions.Value.Value.IgnoredControllersOnModelExclusion;
foreach (var controllerModel in application.Controllers)
{
var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
if (exposeServicesAttr != null && exposeServicesAttr.IncludeSelf)
{
ignoredControllersOnModelExclusion.AddIfNotContains(controllerModel.ControllerType);
var existingControllerModels = application.Controllers
.Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType));
controllerModelsToRemove.AddIfNotContains(existingControllerModels);
}
}
application.Controllers.RemoveAll(controllerModelsToRemove);
}
}
In your module, insert ExtendedServiceApplicationModelConvention before AbpServiceConventionWrapper:
public override void ConfigureServices(ServiceConfigurationContext context)
{
// ...
Configure<MvcOptions>(options =>
{
var abpServiceConvention = options.Conventions.OfType<AbpServiceConventionWrapper>().First();
options.Conventions.InsertBefore(abpServiceConvention, new ExtendedControllerApplicationModelConvention (context.Services));
});
}
I created a test project using the same version of ABP v3.3.2 and managed to get this working.
You can override the original methods in a new class that inherits from the original IdentityUserController, but you need to create your own controller to 'add' new methods to it. If you create a new controller that includes the same class attributes as IdentityUserController then it will appear like it has been extended.
[RemoteService(Name = IdentityRemoteServiceConsts.RemoteServiceName)]
[Area("identity")]
[ControllerName("User")]
[Route("api/identity/users")]
[ExposeServices(typeof(MyIdentityUserController))]
public class MyIdentityUserController : AbpController, IApplicationService, IRemoteService
{
[HttpGet("my-method")]
public Task<string> MyMethod()
{
return Task.FromResult("Works");
}
}

Accessing Dropbox from Xamarin Forms using PKCE OAuth and .NET API - solution

Implementing Dropbox support in Xamarin Forms was, let’s say, interesting, especially using the more secure PKCE OAuth flow, which requires deep linking, as WebView is insecure.
For anyone struggling as much as I was, working code is shown below, including shared code and Android code. I haven’t needed to implement the iOS side as I’m using iCloud rather than Dropbox there, but that should be straightforward.
You may want to add an ActivityIndicator to the calling page, as it pops in and out of view during authorization.
Note: While the Dropbox .NET API is not officially supported for Xamarin, it can be made to work, as shown here.
EDIT 18 Sep 2021:
Added code to (1) handle case where user declines to accept access to Dropbox and (2) close the browser after authorization. A remaining issue: each time we authorize, a tab gets added to the browser - don't see how to overcome that.
ANDROID CODE
using System;
using System.Net;
using System.Threading.Tasks;
using Xamarin.Forms;
using Android.Content;
using Android.App;
using Plugin.CurrentActivity;
using MyApp.Droid.DropboxAuth;
using AndroidX.Activity;
[assembly: Dependency (typeof (DropboxOAuth2_Android))]
namespace MyApp.Droid.DropboxAuth
{
public class DropboxOAuth2_Android: Activity, IDropbox
{
public bool IsBrowserInstalled ()
// Returns true if a web browser is installed
{
string url = "https://google.com"; // Any url will do
Android.Net.Uri webAddress = Android.Net.Uri.Parse ( url );
Intent intentWeb = new Intent ( Intent.ActionView, webAddress );
Context currentContext = CrossCurrentActivity.Current.Activity;
Android.Content.PM.PackageManager packageManager = currentContext.PackageManager;
return intentWeb.ResolveActivity ( packageManager ) != null;
}
public void OpenBrowser ( string url )
// Opens default browser
{
Intent intent = new Intent ( Intent.ActionView, Android.Net.Uri.Parse ( url ) );
Context currentContext = CrossCurrentActivity.Current.Activity;
currentContext.StartActivity ( intent );
}
public void CloseBrowser ()
// Close the browser
{
Finish ();
}
}
}
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Content.PM;
using MyApp.DropboxService;
namespace MyApp.Droid.DropboxAuth
{
public class Redirection_Android
{
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[IntentFilter ( new [] { Intent.ActionView },
Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
DataScheme = "com.mydomain.myapp" )]
public class RedirectHandler : Activity
{
protected async override void OnCreate ( Bundle savedInstanceState )
{
base.OnCreate( savedInstanceState );
Intent intent = Intent; // The intent that started this activity
if ( Intent.Action == Intent.ActionView )
{
Android.Net.Uri uri = intent.Data;
if ( uri.ToString ().Contains ("The+user+chose+not+to+give+your+app+access" ) )
{
// User pressed Cancel not Accept
if ( MyApp.DropboxService.Authorization.Semaphore != null )
{
// Release semaphore
Behayve.DropboxService.Authorization.Semaphore.Release ();
Behayve.DropboxService.Authorization.Semaphore.Dispose ();
Behayve.DropboxService.Authorization.Semaphore = null;
}
Xamarin.Forms.DependencyService.Get<IDropbox> ().CloseBrowser ();
Finish ();
return;
}
if ( uri.GetQueryParameter ( "state" ) != null )
{
// Protect from curious eyes
if ( uri.GetQueryParameter ( "state" ) != Authorization.StatePKCE )
Finish ();
if ( uri.GetQueryParameter ( "code" ) != null )
{
string code = uri.GetQueryParameter ( "code" );
// Perform stage 2 flow, storing tokens in settings
bool success = await Authorization.Stage2FlowAsync ( code );
Authorization.IsAuthorizationComplete = true;
// Allow shared code that initiated this activity to continue
Authorization.Semaphore.Release ();
}
}
}
Finish ();
}
}
}
}
NOTE: If targeting API 30 or later, add the following to your manifest within the <queries> tag:
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
SHARED CODE
using System;
namespace MyApp
{
public interface IDropbox
{
bool IsBrowserInstalled (); // True if a browser is installed
void OpenBrowser ( string url ); // Opens url in internal browser
void CloseBrowser (); // Closes the browser
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using Xamarin.Forms;
using MyApp.Resx;
using Dropbox.Api;
namespace MyApp.DropboxService
{
public class Authorization
{
private const string packageName = "com.mydomain.myapp"; // Copied from Android manifest
private const string redirectUri = packageName + ":/oauth2redirect";
private static PKCEOAuthFlow pkce;
private const string clientId = “abcabcabcabcabc”; // From Dropbox app console
private static DropboxClientConfig dropboxClientConfig;
// Settings keys
private const string accessTokenKey = "accessTokenKey";
public const string refreshTokenKey = "refreshTokenKey";
private const string userIdKey = "userIdKey";
public static string StatePKCE {get; private set; }
public static SemaphoreSlim Semaphore { get; set; } // Allows shared code to wait for redirect-triggered Android activity to complete
public static volatile bool IsAuthorizationComplete; // Authorization is complete, tokens stored in settings
public Authorization ()
{
IsAuthorizationComplete = false;
Semaphore = new SemaphoreSlim ( 1,1 );
}
public async Task<DropboxClient> GetAuthorizedDropBoxClientAsync ()
// If access tokens not already stored in secure settings, first verifies a browser is installed,
// then after a browser-based user authorisation dialog, securely stores access token, refresh token and user ID in settings.
// Returns a long-lived authorised DropboxClient (based on a refresh token stored in settings).
// Returns null if not authorised or no browser or if user hit Cancel or Back (no token stored).
// Operations can then be performed on user's Dropbox over time via the DropboxClient.
//
// Assumes caller has verified Internet is available.
//
// Employs the PKCE OAuth flow.
// WebView is not used because of associated security issues -- deep linking is used instead.
// The tokens can be retrieved from settings any time should they be desired.
// No auxiliary website is used.
{
if ( string.IsNullOrEmpty ( await Utility.GetSettingAsync ( refreshTokenKey ) ) )
{
// We do not yet have a refresh key
try
{
// Verify user has a suitable browser installed
if ( ! DependencyService.Get<IDropbox> ().IsBrowserInstalled () )
{
await App.NavPage.DisplayAlert ( T.NoBrowserInstalled, T.InstallBrowser, T.ButtonOK );
return null;
}
// Stage 1 flow
IsAuthorizationComplete = false;
DropboxCertHelper.InitializeCertPinning ();
pkce = new PKCEOAuthFlow (); // Generates code verifier and code challenge for PKCE
StatePKCE = Guid.NewGuid ().ToString ( "N" );
// NOTE: Here authorizeRedirectUI is of the form com.mydomain.myapp:/oauth2redirect
Uri authorizeUri = pkce.GetAuthorizeUri ( OAuthResponseType.Code, clientId: clientId, redirectUri:redirectUri,
state: StatePKCE, tokenAccessType: TokenAccessType.Offline, scopeList: null, includeGrantedScopes: IncludeGrantedScopes.None );
// NOTE: authorizeUri looks like this:
// https://www.dropbox.com/oauth2/authorize?response_type=code&client_id=abcabcabcabcabc&redirect_uri=com.mydomain.myapp%3A%2Foauth2redirect&state=51cbbd2b7bce4d7990bc72fc95991375&token_access_type=offline&code_challenge_method=S256&code_challenge=r75HUStz-F43vWl2yr9m5ctgF1lgE7uqu-cf_gQpSEU
// Open authorization url in browser
await Semaphore.WaitAsync (); // Take semaphore
DependencyService.Get<IDropbox> ().OpenBrowser ( authorizeUri.AbsoluteUri );
// Wait until Android redirection activity obtains tokens and releases semaphore
// NOTE: User might first press Cancel or Back button - this returns user to page calling this method, where OnAppearing will run
await Semaphore.WaitAsync ();
}
catch
{
if ( Semaphore != null )
Semaphore.Dispose ();
return null;
}
}
else
IsAuthorizationComplete = true;
// Wrap up
if ( Semaphore != null )
Semaphore.Dispose ();
if ( IsAuthorizationComplete )
{
// Return authorised Dropbox client
DropboxClient dropboxClient = await AuthorizedDropboxClientAsync ();
DependencyService.Get<IDropbox> ().CloseBrowser ();
return dropboxClient;
}
return null;
}
public static async Task<bool> Stage2FlowAsync ( string code )
// Obtains authorization token, refresh token and user Id, and
// stores them in settings.
// code = authorization code obtained in stage 1 flow
// Returns true if tokens obtained
{
// Retrieve tokens
OAuth2Response response = await pkce.ProcessCodeFlowAsync ( code, clientId, redirectUri: redirectUri );
if ( response == null )
return false;
string accessToken = response.AccessToken;
string refreshToken = response.RefreshToken;
string userId = response.Uid;
// Save tokens in settings
await Utility.SetSettingAsync ( accessTokenKey, accessToken );
await Utility.SetSettingAsync ( refreshTokenKey, refreshToken );
await Utility.SetSettingAsync ( userIdKey, userId );
return true;
}
public static async Task<DropboxClient> AuthorizedDropboxClientAsync ( )
// Returns authorized Dropbox client, or null if none available
// For use when Dropbox authorization has already taken place
{
string refreshToken = await Utility.GetSettingAsync ( Authorization.refreshTokenKey );
// NOTE: Due to Dropbox.NET API bug for Xamarin, we need to override Android Build HttpClientImplementation setting (AndroidClientHandler) with HTTPClientHandler, for downloads to work
dropboxClientConfig = new DropboxClientConfig () { HttpClient = new HttpClient ( new HttpClientHandler () ) };
return new DropboxClient ( refreshToken, clientId, dropboxClientConfig );
}
public static async Task ClearTokensInSettingsAsync ()
// Clears access token, refresh token, user Id token
// Called when app initialises
{
await Utility.SetSettingAsync ( accessTokenKey, string.Empty );
await Utility.SetSettingAsync ( refreshTokenKey, string.Empty );
await Utility.SetSettingAsync ( userIdKey, string.Empty );
}
public static async Task<bool> IsLoggedInAsync ()
// Returns true if logged in to Dropbox
{
if ( await Utility.GetSettingAsync ( refreshTokenKey ) == string.Empty )
return false;
return true;
}
}
}
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Dropbox.Api;
using Dropbox.Api.Files;
using MyApp.Resx;
namespace MyApp.DropboxService
{
public class FileHelper
{
const string _FNF = “~FNF”;
public static async Task<bool> ExistsAsync ( DropboxClient dbx, string path )
// Returns true if given filepath/folderpath exists for given Dropbox client
// Dropbox requires "/" to be the initial character
{
try
{
GetMetadataArg getMetadataArg = new GetMetadataArg ( path );
Metadata xx = await dbx.Files.GetMetadataAsync ( getMetadataArg );
}
catch ( Exception ex )
{
if ( ex.Message.Contains ( "not_found" ) ) // Seems no other way to do it
return false;
await Utility.WriteLogFileAsync ( T.Exception, ex.AsString () );
throw new Exception ( "In FileHelper.ExistsAsync " + ex.ToString (), ex.InnerException );
}
return true;
}
public static async Task<CreateFolderResult> CreateFolderAsync ( DropboxClient dbx, string path )
// Creates folder for given Dropbox user at given path, unless it already exists
// Returns CreateFolderResult, or null if already exists
{
try
{
if ( await ExistsAsync ( dbx, path ) )
return null;
CreateFolderArg folderArg = new CreateFolderArg( path );
return await dbx.Files.CreateFolderV2Async( folderArg );
}
catch ( Exception ex )
{
await Utility.WriteLogFileAsync ( T.Exception, ex.AsString () );
throw new Exception ( "In FileHelper.CreateFolderAsync " + ex.ToString (), ex.InnerException );
}
}
public static async Task DeleteFileAsync ( DropboxClient dbx, string path )
// Delete given Dropbox user's given file
{
try
{
DeleteArg deleteArg = new DeleteArg ( path );
await dbx.Files.DeleteV2Async ( deleteArg );
}
catch ( Exception ex )
{
await Utility.WriteLogFileAsync ( T.Exception, ex.AsString () );
throw new Exception ( "In FileHelper.DeleteFileAsync " + ex.ToString (), ex.InnerException );
}
}
public static async Task<FileMetadata> UploadBinaryFileAsync ( DropboxClient dbx, string localFilepath, string dropboxFilepath )
// Copies given local binary file to given Dropbox file, deleting any pre-existing destination file
// NOTE: Dropbox requires initial "/" in dropboxFilePath
{
int tries = 0;
while ( tries < 30 )
{
try
{
if ( await ExistsAsync ( dbx, dropboxFilepath ) )
await DeleteFileAsync ( dbx, dropboxFilepath );
using ( FileStream localStream = new FileStream ( localFilepath, FileMode.Open, FileAccess.Read ) )
{
return await dbx.Files.UploadAsync ( dropboxFilepath,
WriteMode.Overwrite.Instance,
body: localStream );
}
}
catch ( RateLimitException ex )
{
// We have to back off and retry later
int backoffSeconds= ex.RetryAfter; // >= 0
System.Diagnostics.Debug.WriteLine ( "****** Dropbox requested backoff of " + backoffSeconds.ToString () + " seconds" );
await Task.Delay ( backoffSeconds * 1000 );
}
catch ( Exception ex )
{
await Utility.WriteLogFileAsync ( T.Exception, ex.AsString () );
throw new Exception ( "In FileHelper.UploadBinaryFileAsync " + ex.ToString (), ex.InnerException );
}
tries++;
}
return null;
}
public static async Task<FileMetadata> UploadTextFileAsync ( DropboxClient dbx, string localFilepath, string dropboxFilepath )
// Copies given local text file to given Dropbox file, deleting any pre-existing destination file
{
int tries = 0;
while ( tries < 30 )
{
try
{
if ( await ExistsAsync ( dbx, dropboxFilepath ) )
await DeleteFileAsync ( dbx, dropboxFilepath );
string fileContents = File.ReadAllText ( localFilepath );
using ( MemoryStream localStream = new MemoryStream ( Encoding.UTF8.GetBytes ( fileContents ) ) )
{
return await dbx.Files.UploadAsync ( dropboxFilepath,
WriteMode.Overwrite.Instance,
body: localStream );
}
}
catch ( RateLimitException ex )
{
// We have to back off and retry later
int backoffSeconds= ex.RetryAfter; // >= 0
System.Diagnostics.Debug.WriteLine ( "****** Dropbox requested backoff of " + backoffSeconds.ToString () + " seconds" );
await Task.Delay ( backoffSeconds * 1000 );
}
catch ( Exception ex )
{
await Utility.WriteLogFileAsync ( T.Exception, ex.AsString () );
throw new Exception ( "In FileHelper.UploadTextFileAsync " + ex.ToString (), ex.InnerException );
}
tries++;
}
return null;
}
public static async Task<bool> DownloadFileAsync ( DropboxClient dbx, string dropboxFilepath, string localFilepath )
// Copies given Dropbox file to given local file, deleting any pre-existing destination file
// Returns true if successful
// NOTE: Dropbox requires initial "/" in dropboxFilePath
{
int tries = 0;
while ( tries < 30 )
{
try
{
// If destination exists, delete it
if ( File.Exists ( localFilepath ) )
File.Delete ( localFilepath );
// Copy file
using ( var response = await dbx.Files.DownloadAsync ( dropboxFilepath ) )
{
using ( FileStream fileStream = File.Create ( localFilepath ) )
{
( await response.GetContentAsStreamAsync() ).CopyTo ( fileStream );
}
}
return true;
}
catch ( RateLimitException ex )
{
// We have to back off and retry later
int backoffSeconds= ex.RetryAfter; // >= 0
System.Diagnostics.Debug.WriteLine ( "****** Dropbox requested backoff of " + backoffSeconds.ToString () + " seconds" );
await Task.Delay ( backoffSeconds * 1000 );
}
catch ( Exception ex )
{
await Utility.WriteLogFileAsync ( T.Exception, ex.AsString () );
}
tries++;
}
return false;
}
public static async Task EnsureSubfolderExistsAsync ( DropboxClient dbx, string subfolderPath )
// Creates given subfolder for given client unless it already exists
{
if ( await ExistsAsync ( dbx, subfolderPath ) )
return;
await CreateFolderAsync ( dbx, subfolderPath);
}
}
}
using Xamarin.Forms;
using Xamarin.Essentials;
namespace MyApp
{
public class Utility
{
public static async Task SetSettingAsync ( string key, string settingValue )
// Stores given value in setting whose key is given
// Uses secure storage if possible, otherwise uses preferences
{
try
{
await SecureStorage.SetAsync ( key, settingValue );
}
catch
{
// On some Android devices, secure storage is not supported - here if that is the case
// Use preferences
Preferences.Set ( key, settingValue );
}
}
public static async Task<string> GetSettingAsync ( string key )
// Returns setting with given name, or null if unavailable
// Uses secure storage if possible, otherwise uses preferences
{
string settingValue;
try
{
settingValue = await SecureStorage.GetAsync ( key );
}
catch
{
// Secure storage is unavailable on this device so use preferences
settingValue = Preferences.Get ( key, defaultValue: null );
}
return settingValue;
}
In the Dropbox app console, permission type is Scoped App (App Folder), and permissions are files.content.write and files.content.read.

Async Vala Example

In the Book "Introduction to Vala" by Dr Michael Lauer, he has mentioned that the lib Soup async api is broken. I'm struggling to write a simple example using session.queue_message that query radio stations using the service from radio-browser. Here is my code. I would appreciate any help form experienced Programmers like "Al Thomas". Thank you.
public class Station : Object {
// A globally unique identifier for the change of the station information
public string changeuuid { get; set; default = ""; }
// A globally unique identifier for the station
public string stationuuid { get; set; default = ""; }
// The name of the station
public string name { get; set; default = ""; }
// The stream URL provided by the user
public string url { get; set; default = ""; }
// and so on ... many properties
public string to_string () {
var builder = new StringBuilder ();
builder.append_printf ("\nchangeuuid = %s\n", changeuuid);
builder.append_printf ("stationuuid = %s\n", stationuuid);
builder.append_printf ("name = %s\n", name);
builder.append_printf ("url = %s\n", url);
return (owned) builder.str;
}
}
public class RadioBrowser : Object {
private static Soup.Session session;
// private static MainLoop main_loop;
public const string API_URL = "https://de1.api.radio-browser.info/json/stations";
public const string USER_AGENT = "github.aeldemery.radiolibrary";
public RadioBrowser (string user_agent = USER_AGENT, uint timeout = 50)
requires (timeout > 0)
{
Intl.setlocale ();
session = new Soup.Session ();
session.timeout = timeout;
session.user_agent = user_agent;
session.use_thread_context = true;
// main_loop = new MainLoop ();
}
private void check_response_status (Soup.Message msg) {
if (msg.status_code != 200) {
var str = "Error: Status message error %s.".printf (msg.reason_phrase);
error (str);
}
}
public Gee.ArrayList<Station> listStations () {
var stations = new Gee.ArrayList<Station> ();
var data_list = Datalist<string> ();
data_list.set_data ("limit", "100");
var parser = new Json.Parser ();
parser.array_element.connect ((pars, array, index) => {
var station = Json.gobject_deserialize (typeof (Station), array.get_element (index)) as Station;
assert_nonnull (station);
stations.add (station);
});
var msg = Soup.Form.request_new_from_datalist (
"POST",
API_URL,
data_list
);
// send_message works but not queue_message
// session.send_message (msg);
session.queue_message (msg, (sess, mess) => {
check_response_status (msg);
try {
parser.load_from_data ((string) msg.response_body.flatten ().data);
} catch (Error e) {
error ("Failed to parse data, error:" + e.message);
}
});
return stations;
}
}
int main (string[] args) {
var radio_browser = new RadioBrowser ();
var stations = radio_browser.listStations ();
assert_nonnull (stations);
foreach (var station in stations) {
print (station.to_string ());
}
return 0;
}
While I'm not Al Thomas, I still might be able to help. ;)
For async calls to work in there needs to be a main loop running, and typically from the main program thread. Thus you want to create and execute the main loop from your main() function, rather than in your application code:
int main (string[] args) {
var loop = new GLib.MainLoop ();
var radio_browser = new RadioBrowser ();
// set up async calls here
// then set the main loop running as the last thing
loop.run();
}
Also, if you want to wait for an async call to complete, you typically need to make the call using the yield keyword from another async function E.g:
public async Gee.ArrayList<Station> listStations () {
…
// When this call is made, execution of listStations() will be
// suspended until the soup response is received
yield session.send_async(msg);
// Execution then resumes normally
check_response_status (msg);
parser.load_from_data ((string) msg.response_body.flatten ().data);
…
return stations;
}
You can then call this from the (non-async) main function using the listStations.begin(…) notation:
int main (string[] args) {
var loop = new GLib.MainLoop ();
var radio_browser = new RadioBrowser ();
radio_browser.listStations.begin((obj, res) => {
var stations = radio_browser.listStations.end(res);
…
loop.quit();
});
loop.run();
}
As further reading, I would recommend the async section of the Vala Tutorial, and the asyc examples on the wiki as well.

How to call actual service instead of local JSON in Getting Started Demos complete example (search,sort,page,and filter)

Working with complete table example (with sorting, filtering and pagination) that simulates a server call using a JSON constant.
I am trying to make it call a real API that returns JSON instead of a local constant.
I was successful in changing the code from the demo from using countries to suppliers. For example countries.ts is suppliers.ts, country.ts is supplier.ts, and country.service is supplier.service.
That works with no problems, but I want to remove the countries.ts (suppliers.ts in my case), export the JSON and replace it with a http.get call to a local API service.
Here's a sample of a working code from the API service that I am trying to call:
getSuppliers(): Observable<SupplierVM[]> {
return this.http.get<SupplierVM[]>(apiUrl+'supplier')
.pipe(
tap(heroes => console.log('fetched Suppliers')),
catchError(this.handleError('getSuppliers', []))
);
}
Here is a sample of a working call from within an Angular component:
allSuppliers:Observable<SupplierVM[]>;
this.allSuppliers=this.api.getSuppliers();
This is the method that does the work in the demo (the only difference is that I am using suppliers instead of countries)
private _search(): Observable<SearchResult> {
const {sortColumn, sortDirection, pageSize, page, searchTerm} = this._state;
//1. sort
let suppliers = sort(SUPPLIERS, sortColumn, sortDirection);
//2. filter
suppliers = suppliers.filter(country => matches(country, searchTerm/*, this.pipe*/));
const total = suppliers.length;
//3. paginate
suppliers = suppliers.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);
return of({suppliers, total});
}
This works when I call suppliers from the import statement, but I want to replace the suppliers from the sort method to something like this.allSuppliers (kind of like the sample method call above).
//1. sort
let suppliers = sort(this.allSuppliers, sortColumn, sortDirection);
Everything works when using local imported constant composed of JSON and should work just the same when calling actual service because the JSON response is the exact same.
I had a go at this and ended up implementing a switchable server-side/client-side sorting/pagination/filtering system (Angular 12).
Base Service: All services are derived from this, even ones without sorting/pagination/filtering.
import { HttpClient, HttpParams } from "#angular/common/http";
import { Observable } from "rxjs";
export abstract class BaseService<T> {
constructor(protected _http: HttpClient, protected actionUrl: string) {
}
getAll(params?: HttpParams): Observable<T[]> {
if (params)
return this._http.get(this.actionUrl, { params: params }) as Observable<T[]>;
return this._http.get(this.actionUrl) as Observable<T[]>;
}
getSingle(id: number, params?: HttpParams): Observable<T> {
if (params)
return this._http.get(`${this.actionUrl}/${id}`, { params: params }) as Observable<T>;
return this._http.get(`${this.actionUrl}/${id}`) as Observable<T>;
}
push(content: any, id: number) {
if (id === 0)
return this._http.post<any>(`${this.actionUrl}`, content);
return this._http.post<any>(`${this.actionUrl}/${id}`, content);
}
post(content: any) {
return this._http.post<any>(`${this.actionUrl}`, content);
}
}
Sortable Service: Services with a sortable/paginated/filtered result set are always derived from this base service. Derived services need to implement their own matching algorithm (_matches()).
import { HttpClient } from "#angular/common/http";
import { BehaviorSubject, Observable, Subject } from "rxjs";
import { BaseService } from "./base.service";
import { SearchResult, SearchState } from "#app/common/_models";
import { SortDirection } from '#app/common/_directives';
import { debounceTime, delay, switchMap, tap } from "rxjs/operators";
export abstract class SortableService<T> extends BaseService<T> {
private _loading$ = new BehaviorSubject<boolean>(true);
private _search$ = new Subject<void>();
private _items$ = new BehaviorSubject<T[]>([]);
private _total$ = new BehaviorSubject<number>(0);
protected _state: SearchState = {
page: 1,
pageSize: 10,
searchTerm: '',
sortColumn: '',
sortDirection: ''
};
constructor(protected _http: HttpClient, protected actionUrl:string) {
super(_http, actionUrl);
this._search$.pipe(
tap(() => this._loading$.next(true)),
debounceTime(200),
switchMap(() => this._search()),
tap(() => this._loading$.next(false))
).subscribe(response => {
this._items$.next(response.result);
this._total$.next(response.total);
});
this._search$.next();
}
get items$() { return this._items$.asObservable(); }
get total$() { return this._total$.asObservable(); }
get loading$() { return this._loading$.asObservable(); }
get page() { return this._state.page; }
get pageSize() { return this._state.pageSize; }
get searchTerm() { return this._state.searchTerm; }
set page(page: number) { this._set({ page }); }
set pageSize(pageSize: number) { this._set({ pageSize }); }
set searchTerm(searchTerm: string) { this._set({ searchTerm }); }
set sortColumn(sortColumn: string) { this._set({ sortColumn }); }
set sortDirection(sortDirection: SortDirection) { this._set({ sortDirection }); }
private _set(patch: Partial<SearchState>) {
Object.assign(this._state, patch);
this._search$.next();
}
protected _compare(v1: string | number | boolean | T[keyof T], v2: string | number | boolean | T[keyof T]) {
return v1 < v2 ? -1 : v1 > v2 ? 1 : 0;
}
private _getValue<T>(obj: T, column: string) {
var parts = column.split('.');
var result : any = obj;
for (let part of parts) {
for (let res in result) {
if (res === part)
result = result[res];
}
}
return result;
}
protected _sort(items: T[], column: string, direction: string): T[] {
if (direction === '' || column === '')
return items;
return [...items].sort((a, b) => {
var aa = this._getValue(a, column);
var bb = this._getValue(b, column);
const res = aa === undefined ? -1 : bb === undefined ? 1 : this._compare(aa, bb);
return direction === 'asc' ? res : -res;
});
}
protected abstract _matches(items: T, term: string) : boolean;
protected abstract _search(): Observable<SearchResult<T>>;
}
Client-Sortable Service: If the list manipulation happens in the client, derive the services from this one. I use a simple 1-minute cache to hold data.
import { HttpClient } from "#angular/common/http";
import { Observable, of } from "rxjs";
import { mergeMap } from "rxjs/operators";
import { SortableService } from "./sortable.service";
import { SearchResult } from "#app/common/_models";
export abstract class ClientSortableService<T> extends SortableService<T> {
cache?: T[];
cacheUpdatedIn?: Date;
constructor(protected _http: HttpClient, protected actionUrl: string) {
super(_http, actionUrl);
}
private getCache(): Observable<T[]> {
var expected = new Date();
if (this.cacheUpdatedIn !== undefined) {
expected = this.cacheUpdatedIn;
expected.setMinutes(expected.getMinutes() + 1);
}
//Search again.
if (this.cache == undefined || this.cache.length == 0 || expected == undefined || expected < new Date())
{
this.cacheUpdatedIn = new Date();
return this.getAll();
}
return of(this.cache || []);
}
protected _search(): Observable<SearchResult<T>> {
return this.getCache().pipe(mergeMap(items => {
this.cache = items;
//1: Sort.
let siteGroups = this._sort(items, this._state.sortColumn, this._state.sortDirection);
//2: Filter.
siteGroups = siteGroups.filter(group => this._matches(group, this._state.searchTerm));
const total = siteGroups.length;
//3: Paginate.
siteGroups = siteGroups.slice((this._state.page - 1) * this._state.pageSize, (this._state.page - 1) * this._state.pageSize + this._state.pageSize);
return of({ result: siteGroups, total: total });
}));
}
}
Server-Sortable Service: If the list manipulation happens in the server, derive the services from this one. The api need to handle the results before returning it.
import { HttpClient } from "#angular/common/http";
import { Observable, of } from "rxjs";
import { SortableService } from "./sortable.service";
import { SearchResult } from "#app/common/_models";
export abstract class ServerSortableService<T> extends SortableService<T> {
constructor(protected _http: HttpClient, protected actionUrl: string) {
super(_http, actionUrl);
}
protected _search(): Observable<SearchResult<T>> {
return super.post(this._state);
}
}
Custom Service: This custom service derives from the client-side base service. See that I need to declare a method that is used for the filtering, to select which fields to compare to.
#Injectable({ providedIn: 'root' })
export class ExampleService extends ClientSortableService<Example> {
constructor(protected _http: HttpClient) {
super(_http, `${environment.apiUrl}/sites`);
}
protected _matches(items: Example, term: string): boolean {
return items.displayName.toLowerCase().includes(term.toLowerCase());
}
}
So I beat my head against this for over a day.. I had it but I was doing something silly that broke pagination.
Anyway, what I did was all client-side since my data isn't going to be huge. In the constructor I wrapped the search.next() call in the subscribe of the API call. I also assigned the results to an array there. That way I had data when the search.next() was called. That array is what the actual function manipulates.
If you can follow my bad code here are the relevant snippets:
Declare a new variable to hold the results.
private myData: MyDataType[] = [];
In the constructor add your API call to get your data.
this.httpClient.get<MyDataType[]>('GetMyData').subscribe(data => {
this.myData = data;
this._search$.next();
});
This will load your data client-side to be used by the search function and trigger the initial call to said function.
And finally, in the search function, use your newly created and data-loaded variable for sorting and filtering.
//1. Sort
let theData = sort(this.myData, sortColumn, sortDirection);
I don't know what I was trying to do at first.. I went around the world and googled for several hours and found nothing that made it seem so simple. Just a few lines of code was all it took!

How can i use engine object in my console application

"How can i use engine in my console application"
I shouldn't use the ITemplate-interface and Transform-Method.
I am using Tridion 2011
Could anyone please suggest me.
You can't. The Engine class is part of the TOM.NET and that API is explicitly reserved for use in:
Template Building Blocks
Event Handlers
For all other cases (such as console applications) you should use the Core Service.
There are many good questions (and articles on other web sites) already:
https://stackoverflow.com/search?q=%5Btridion%5D+core+service
http://www.google.com/#q=tridion+core+service
If you get stuck along the way, show us the relevant code+configuration you have and what error message your get (or at what step you are stuck) and we'll try to help from there.
From a console application you should use the Core Service. I wrote a small example using the Core Service to search for items in the content manager.
Console.WriteLine("FullTextQuery:");
var fullTextQuery = Console.ReadLine();
if (String.IsNullOrWhiteSpace(fullTextQuery) || fullTextQuery.Equals(":q", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine("SearchIn IdRef:");
var searchInIdRef = Console.ReadLine();
var queryData = new SearchQueryData
{
FullTextQuery = fullTextQuery,
SearchIn = new LinkToIdentifiableObjectData
{
IdRef = searchInIdRef
}
};
var results = coreServiceClient.GetSearchResults(queryData);
results.ToList().ForEach(result => Console.WriteLine("{0} ({1})", result.Title, result.Id));
Add a reference to Tridion.ContentManager.CoreService.Client to your Visual Studio Project.
Code of the Core Service Client Provider:
public interface ICoreServiceProvider
{
CoreServiceClient GetCoreServiceClient();
}
public class CoreServiceDefaultProvider : ICoreServiceProvider
{
private CoreServiceClient _client;
public CoreServiceClient GetCoreServiceClient()
{
return _client ?? (_client = new CoreServiceClient());
}
}
And the client itself:
public class CoreServiceClient : IDisposable
{
public SessionAwareCoreServiceClient ProxyClient;
private const string DefaultEndpointName = "netTcp_2011";
public CoreServiceClient(string endPointName)
{
if(string.IsNullOrWhiteSpace(endPointName))
{
throw new ArgumentNullException("endPointName", "EndPointName is not specified.");
}
ProxyClient = new SessionAwareCoreServiceClient(endPointName);
}
public CoreServiceClient() : this(DefaultEndpointName) { }
public string GetApiVersionNumber()
{
return ProxyClient.GetApiVersion();
}
public IdentifiableObjectData[] GetSearchResults(SearchQueryData filter)
{
return ProxyClient.GetSearchResults(filter);
}
public IdentifiableObjectData Read(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
public ApplicationData ReadApplicationData(string subjectId, string applicationId)
{
return ProxyClient.ReadApplicationData(subjectId, applicationId);
}
public void Dispose()
{
if (ProxyClient.State == CommunicationState.Faulted)
{
ProxyClient.Abort();
}
else
{
ProxyClient.Close();
}
}
}
When you want to perform CRUD actions through the core service you can implement the following methods in the client:
public IdentifiableObjectData CreateItem(IdentifiableObjectData data)
{
data = ProxyClient.Create(data, new ReadOptions());
return data;
}
public IdentifiableObjectData UpdateItem(IdentifiableObjectData data)
{
data = ProxyClient.Update(data, new ReadOptions());
return data;
}
public IdentifiableObjectData ReadItem(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
To construct a data object of e.g. a Component you can implement a Component Builder class that implements a create method that does this for you:
public ComponentData Create(string folderUri, string title, string content)
{
var data = new ComponentData()
{
Id = "tcm:0-0-0",
Title = title,
Content = content,
LocationInfo = new LocationInfo()
};
data.LocationInfo.OrganizationalItem = new LinkToOrganizationalItemData
{
IdRef = folderUri
};
using (CoreServiceClient client = provider.GetCoreServiceClient())
{
data = (ComponentData)client.CreateItem(data);
}
return data;
}
Hope this gets you started.

Resources