I am trying to write Xamarin.Forms UI tests using Moq to mock my authentication interface: [previous question][1]. I have refactored my application so that my SignIn(string username, string password) method is inside a class that implements the IAuthService. I am now having issues with mocking the IAuthService to essentially 'replace' the actual sign in verification that occurs when clicking the Sign In button. In my CloudAuthService class (which implements IAuthService), I am authenticating to Amazon Cognito, but I want to mock this result within the UI test so it is not calling the cloud service.
EDIT: after many suggestions, I have decided to include my current implementation below. This still doesn't appear to fully work despite the
output from Console.WriteLine(App.AuthApi.IsMockService()); within the BeforeEachTest() method results in true (as expected).
However, running the same thing within the App() constructor method results in false. So it doesn't appear to be running before the app actually starts, is there a way to have UITest code that runs before the app initializes?
LoginPage
[XamlCompilation(XamlCompilationOptions.Compile)]
public sealed partial class LoginPage
{
private readonly IBiometricAuthentication _bioInterface;
private static readonly Lazy<LoginPage>
Lazy =
new Lazy<LoginPage>
(() => new LoginPage(App.AuthApi));
public static LoginPage Instance => Lazy.Value;
private string _username;
private string _password;
private LoginPageViewModel _viewModel;
private IAuthService _authService;
public LoginPage(IAuthService authService)
{
InitializeComponent();
_authService = authService;
_viewModel = new LoginPageViewModel();
BindingContext = _viewModel;
}
private void LoginButtonClicked(object sender, EventArgs args)
{
_username = UsernameEntry.Text;
_password = PasswordEntry.Text;
LoginToApplication();
}
public async void LoginToApplication()
{
AuthenticationContext context = await _authService.SignIn(_username, _password);
}
}
App Class
public partial class App
{
public static IAuthService AuthApi { get; set; } = new AWSCognito()
public App()
{
Console.WriteLine(AuthApi.IsMockService())
// AuthApi = new AWSCognito(); // AWSCognito implements IAuthService
InitializeComponent();
MainPage = new NavigationPage(new LoginPage(AuthApi));
}
}
Test Class
class LoginPageTest
{
IApp app;
readonly Platform platform;
public LoginPageTest(Platform platform)
{
this.platform = platform;
}
[SetUp]
public void BeforeEachTest()
{
var mocker = new Mock<IAuthService>();
var response = new AuthenticationContext(CognitoResult.Ok)
{
IdToken = "SUCCESS_TOKEN"
};
mocker.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(() => new MockAuthService().SignIn("a", "a"));
mocker.Setup(x => x.IsMockService()).Returns(() => new MockAuthService().IsMockService());
App.AuthApi = mocker.Object;
Console.WriteLine(App.AuthApi.IsMockService());
app = AppInitializer.StartApp(platform);
}
[Test]
public void ClickingLoginWithUsernameAndPasswordStartsLoading()
{
app.WaitForElement(c => c.Marked("Welcome"));
app.EnterText(c => c.Marked("Username"), new string('a', 1));
app.EnterText(c => c.Marked("Password"), new string('a', 1));
app.Tap("Login");
bool state = app.Query(c => c.Class("ProgressBar")).FirstOrDefault().Enabled;
Assert.IsTrue(state);
}
}
Your problem seems to be that you've injected the mock after you run through the test. This means when it's executing it's using the original AuthService. If we rearrange the code to move the injection before anything gets executed we should see the result we expect:
// let's bring this mock injection up here
var mocker = new Mock<IAuthService>();
mocker.Setup(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(response)).Verifiable();
App.AuthApi = mocker.Object;
// now we try to login, which should call the mock methods of the auth service
app.WaitForElement(c => c.Marked("Welcome to Manuly!"));
app.EnterText(c => c.Marked("Username"), new string('a', 1));
app.EnterText(c => c.Marked("Password"), new string('a', 1));
app.Tap("Login");
var response = new AuthenticationContext(CognitoResult.Ok)
{
IdToken = "SUCCESS_TOKEN",
};
bool state = app.Query(c => c.Class("ProgressBar")).FirstOrDefault().Enabled;
Assert.IsTrue(state);
Now try executing it, and it should do as you desire.
EDIT:
As pointed out in the comments by Nkosi the static Auth service is set in the constructor preventing this.
SO this will need to be changed too:
public partial class App
{
public static IAuthService AuthApi { get; set; } =new AWSCognito(); // assign it here statically
public App()
{
// AuthApi = new AWSCognito(); <-- remove this
InitializeComponent();
MainPage = new NavigationPage(new LoginPage(AuthApi));
}
}
Related
I've been trying to implement integration tests with a database per test strategy, but I haven't been able to make it work as needed.
This is the factory class that uses WebApplicationFactory:
public class TestFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>
where TProgram : class where TDbContext : DbContext
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveDbContext<TDbContext>();
services.AddDbContext<TDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
});
services.EnsureDbCreated<TDbContext>();
});
}
}
This is the TestClass:
public class RolesControllerTest : IDisposable
{
private readonly TestFactory<Program, ADbContext> _factory;
private IServiceScope _scope;
private ADbContext_dbContext;
private readonly HttpClient _client;
private IRoleRepository _rolesRepository;
public RolesControllerTest()
{
_factory = new TestFactory<Program, ADbContext>();
_client = _factory.CreateClient();
_scope = _factory.Services.CreateScope();
var scopedServices = _scope.ServiceProvider;
_dbContext = scopedServices.GetRequiredService<ADbContext>();
_dbContext.Database.EnsureCreated();
}
public void Dispose()
{
_factory.Dispose();
_scope.Dispose();
}
// Tests ...
}
This is the test:
[Fact(DisplayName = "GetAsync returns a list of role models")]
public async Task GetAsync_ReturnsTaskOfRoleModelList()
{
var roleModelInDb = new RoleModel
{
Id = Guid.NewGuid(),
Name = "Role A",
Description = "Role A Description"
};
_rolesRepository = new RoleRepository(_dbContext, TestMapperHelper.GenerateTestMapper());
var roleModel = await _rolesRepository.CreateAsync(roleModelInDb);
var responseData = await _client.GetFromJsonAsync<List<RoleModel>>("/api/roles");
responseData.ShouldNotBeNull();
responseData.ShouldBeOfType<List<RoleModel>>();
responseData.Count.ShouldBe(1);
responseData[0].Id.ShouldBe(roleModel.Id);
responseData[0].Name.ShouldBe(roleModelInDb.Name);
}
The repository returns the expected data: the new roleModel that's been added to the db.
The responseData is a list as expected, but it's empty, so the test fails.
If I try to use a client instead of the repository to create the initial roleModel:
var createdResponse = await _client.PostAsJsonAsync("/api/roles", roleModelInDb);
var createdByClient = await TestResponseHelper.GetResponseContent<RoleModel>(createdResponse);
The createdResponse is a 200 OK Http response, and the role model createdByClient is a valid RoleModel, but the test fails, the list is still empty.
If I use a roleRepository to find the previously created roleModel by Id, the result is null.
If I'm using the same database context for the web factory and repositories, why is this happening?
I am trying to use SimpleInjector in a WPF Application (.NET Framework). We use it in exactly the same way in many of our Services but for some reason when I am attempting to implement the same logic in this WPF Application, the call to the HttpClient().GetAsync is hanging. We think it is because for some reason the Task is not executing.
I am registering the objects from the OnStartUp element of App.xaml.cs as below. Inside the SetupService Constructor we call a SetupService URL (set in the SetupConfiguration Section of the App.Config) to get the SetupResponse to use in the app.
It is ultimately hanging in the ServiceClient.GetAsync method, I have tried to show the flow below:
All classes appear to have been injected correctly, and the ServiceClient is populated in exactly the same way as the same point in one of our working services. We're at a loss as to what is happening, and how to fix this.
Finally, SetupService is being injected in other Classes - so I would rather get it working like this, rather than remove the call from the SimpleInjector mechanism.
Any help is very much appreciated.
public partial class App : Application
{
private static readonly Container _container = new Container();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
RegisterDependencies();
_container.Verify();
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
public class SetupService: ISetupService
{
private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
{
{"Accept", "application/xml"}
};
private const string AuthenticationType = "Basic";
private readonly IServiceClient _serviceClient;
private readonly ILoggingProvider _logger;
private readonly IConfigurationSection _configuration;
public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
{
_serviceClient = serviceClient;
_logger = logger;
_configuration = kmsConfiguration;
RefreshSetup();
}
public void RefreshSetup()
{
try
{
var token = BuildIdentityToken();
var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);
var authenticationHeaderValue =
new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));
_serviceClient.Url = _configuration.Url;
var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;
var responseString = httpResponse.Content.ReadAsStringAsync().Result;
_response = responseString.FromXML<SetupResponse>();
}
catch (Exception e)
{
throw
}
}
public class ServiceClient : IServiceClient
{
private const string ContentType = "application/json";
private string _userAgent;
private ILoggingProvider _logger;
public string Url { get; set; }
public string ProxyAddress { get; set; }
public int TimeoutForRequestAndResponseMs { get; set; }
public int HttpCode { get; private set; }
public ServiceClient(ILoggingProvider logger = null)
{
_logger = logger;
}
public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Url);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
if (authenticationHeaderValue != null)
client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
ProcessHeader(client.DefaultRequestHeaders, additionalData);
var paramsQueryString = ProcessParams(additionalParams);
if (!string.IsNullOrEmpty(paramsQueryString))
endpoint = $"{endpoint}?{paramsQueryString}";
return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**
}
}
}
}
If you block on asynchronous code from a UI thread, then you can expect deadlocks. I explain this fully on my blog. In this case, the cause of the deadlock is Result. There's a couple of solutions.
The one I recommend is to rethink your user experience. Your UI shouldn't be blocking on an HTTP call to complete before it shows anything; instead, immediately (and synchronously) display a UI (i.e., some "loading..." screen), and then update that UI when the HTTP call completes.
The other is to block during startup. There's a few patterns for this. None of them work in all situations, but one that usually works is to wrap the asynchronous work in Task.Run and then block on that, e.g., var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult(); and similar for other blocking calls.
Blocking before showing a UI is generally considered a bad UX. App stores generally disallow it. So that's why I recommend changing the UX. You may find an approach like this helpful.
Thanks for your Responses, I just wanted to sync the solution I've gone for.
It was risky for me to change the code in SetupService to remove the .Result, even though this was probably the correct solution, as I did not want to affect the other working Services using the SetupService library already there.
I ended up moving the regsitrations off the UI Thread by embedding the SimpleInjector code in a Code library, Creating a Program.cs and Main() and setting that as my Entry point.
static class Program
{
public static readonly Container _container = new Container();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(){
var app = new MyApp.App();
Register();
app.Run(_container.GetInstance<MainWindow>());
}
static void Register()
{
_container.Register<MainWindow>();
MySimpleInjector.Register(_container);
_container.Verify();
}
}
and then, in a Separate .dll project, MyApp.Common
public class MySimpleInjector
{
private readonly Container _container;
public static void Register(Container container)
{
var injector = new MySimpleInjector(container);
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
I appreciate that this may not be the ideal solution - but it suits my purposes.
Again, thanks for your help and comments!
Andrew.
This is a follow up question to another post I created around implementing a UI test solution that could toggle which classes to execute code from based on interfaces. The whole goal was to re use test code on versions of apps that are identical (Web vs WPF).
The code compiles fine, but after the test is ran it bombs out on the GetPageModelType method call. Below is my implementation pretty much identical to the linked post, with a few minor adjustments to abstract some of the page object creation on a TestClassBase
UI Test that can determine which classes to execute code from at runtime using interfaces
Interface and corresponding Page Object classes
public interface ILogin
{
void Login(string username, string password);
}
public class WebLogin : ILogin
{
private readonly IWebDriver driver;
public WebLogin(IWebDriver driver)
{
this.driver = driver;
}
public void Login(string username, string password)
{
Console.WriteLine("Web Success!");
}
}
public class WPFLogin : ILogin
{
private readonly WindowsDriver<WindowsElement> session;
public WPFLogin(WindowsDriver<WindowsElement> session)
{
this.session = session;
}
public void Login(string username, string password)
{
Console.WriteLine("WPF Success!");
}
}
Page Object factory classes
public interface IPageModelFactory
{
ILogin CreateLogin();
}
public class WebPageModelFactory : IPageModelFactory
{
private readonly IWebDriver driver;
public WebPageModelFactory(IWebDriver driver)
{
this.driver = driver;
}
public ILogin CreateLogin()
{
return new WebLogin(driver);
}
}
public class WPFPageModelFactory : IPageModelFactory
{
private readonly WindowsDriver<WindowsElement> session;
public WPFPageModelFactory(WindowsDriver<WindowsElement> session)
{
this.session = session;
}
public ILogin CreateLogin()
{
return new WPFLogin(session);
}
}
public class PageModelFactory
{
private readonly object client;
public PageModelFactory(object client)
{
this.client = client;
}
// Create Page Objects
public ILogin CreateLoginPage()
{
var pageModelType = GetPageModelType<ILogin>();
var constructor = pageModelType.GetConstructor(new Type[] { client.GetType() });
return (ILogin)constructor.Invoke(new object[] { client });
}
private Type GetPageModelType<TPageModelInterface>()
{
return client.GetType().Assembly.GetTypes().Single(type => type.IsClass && typeof(TPageModelInterface).IsAssignableFrom(type));
}
}
TestClassBase - base class for tests, simplifies test scripts
[TestFixture]
public class TestClassBase
{
// WinAppDriver variables
private static string WinAppDriverExe = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe";
private string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
// Sessions
public WindowsDriver<WindowsElement> session;
public IWebDriver driver;
// Declare Page Objects
public ILogin login = null;
[SetUp]
public void SetUp()
{
if (GlobalData.targetHost.Equals("WPF"))
{
// Capabilities
AppiumOptions appCapabilities = new AppiumOptions();
appCapabilities.AddAdditionalCapability("app", GetExeFile());
appCapabilities.AddAdditionalCapability("appWorkingDir", GetWorkingDirectory());
// Create Session
session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities, TimeSpan.FromMinutes(3));
session.Manage().Window.Maximize();
// Pass session to page objects
PageModelFactory wpfPages = new PageModelFactory(session);
login = wpfPages.CreateLoginPage();
} else if (GlobalData.targetHost.Equals("Web"))
{
}
}
[TearDown]
public void TearDown()
{
// Clean up code...
}
}
LoginTests
public class LoginTests : TestClassBase
{
[Test]
public void Login()
{
// Login
login.Login("", "");
}
}
Whats not pictured above is my GlobalData.cs class which just contains a bunch of hardcoded variables that are used in the tests. I have the targetHost variable set to "WPF" while testing this against the WPF host. The StartUp code does launch the app as expected, it fails when we call GetPageModelType on PageModelFactory.CreateLoginPage();
I wasn't able to see this in my answer on your original question. The assembly in which the "client" resides and the assembly in which the page models reside are different. That means the PageModelFactory will need a second constructor parameter to know which assembly to search when initializing new page models:
public class PageModelFactory
{
private readonly object client;
private Assembly Assembly => GetType().Assembly;
public PageModelFactory(object client)
{
this.client = client;
}
// Create Page Objects
public ILogin CreateLoginPage()
{
var pageModelTypes = GetPageModelTypes<ILogin>();
var constructorSignature = new Type[] { client.GetType() };
foreach (var type in pageModelTypes)
{
var constructor = type.GetConstructor(constructorSignature);
if (constructor != null)
return (ILogin)constructor.Invoke(new object[] { client });
}
throw new InvalidOperationException($"No class found implementing ILogin with a constructor that accepts {client.GetType().FullName} as an argument in assembly {Assembly.Name}");
}
private IEnumerable<Type> GetPageModelTypes<TPageModelInterface>()
{
return Assembly.GetTypes()
.Where(type => type.IsClass
&& typeof(TPageModelInterface).IsAssignableFrom(type));
}
}
I'm using VS 17 for Xamarin Forms. I've set up Prism in my Xamarin.Forms app and I just added a reference to my Api interface (in ViewModel Constructor) and it makes the app stop navigation to the second page. I need to do this in order to pass parameters etc. I followed this guide:
https://blog.qmatteoq.com/prism-for-xamarin-forms-basic-navigation-and-dependency-injection-part-2/
This is what I did to make the navigation stop working:
private readonly IService _Service;
private ObservableCollection<TodoItem> _topSeries;
public ObservableCollection<TodoItem> TopSeries
{
get { return _topSeries; }
set { SetProperty(ref _topSeries, value); }
}
This is the constructor:
public SecondPageViewModel(IService Service, INavigationService navigationService)
{
_Service = Service;
_navigationService = navigationService;
}
So I cant even reach the above viewmodel because of the above code that I added. I tried to put break points on the DelegateCommand (on first ViewModel) but it just stops after InitializeComponent(); and then nothing happens. No error messages! Thanks!
Update:
My Service class that fetches data:
public class Service : IService
{
public List<TodoItem> TodoList { get; private set; }
HttpClient client;
Service()
{
client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
}
public async Task<List<TodoItem>> DataAsync()
{
TodoList = new List<TodoItem>();
var uri = new Uri(string.Format(Constants.RestUrl, string.Empty));
try
{
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
TodoList = JsonConvert.DeserializeObject<List<TodoItem>>(content);
Debug.WriteLine(content);
}
}
catch (Exception ex)
{
Debug.WriteLine(#"ERROR {0}", ex.Message);
}
return TodoList;
}
}
This is my App.Xaml.cs
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<View.MainPage, MainPageViewModel>();
containerRegistry.RegisterForNavigation<View.SecondPage, SecondPageViewModel>();
containerRegistry.Register<IService, Service>();
}
My Interface:
public interface IService
{
Task<List<TodoItem>> DataAsync();
}
This is how I navigate (click from listview):
private EventItem _selectedEvent { get; set; }
public EventItem SelectedEvent
{
get { return _selectedEvent; }
set
{
if (_selectedEvent != value)
{
if (Device.RuntimePlatform == Device.iOS)
{
_selectedEvent = null;
}
else
{
_selectedEvent = value;
}
NavigationParameters navParams = new NavigationParameters();
navParams.Add("PassedValue", _todoItem.name);
_navigationService.NavigateAsync("SecondPage", navParams);
}
}
}
Edit:
When I debug without the ApiService code the command is taking me to new new constructor in the new viewmodel. With the code it does not reach the contructor.
According to your code you have declared constructor like this:
Service()
{
// ...
}
You didn't set access modifier, therefore the default one is internal. Here is the definition:
Internal types or members are accessible only within files in the same
assembly.
Most likely you have your Service.cs declared in another Assembly and Prism can't access its constructor.
Your navigation doesn't work because dependency injection fails. To fix it, just change your access modifier to public:
public Service()
{
// ...
}
I do requests to Github Api so I have async methods, these do this job. Before it, I always called they in method, that calls from command(actually DelegateCommand). But now I wanna do request in ViewModel because I need to display list on page. I am using Prism to wire view and viewmodel.
Because I can't make viewmodel async, I can't use await word, so I tried to do something like gets result from task, or task.wait. But with this I have the same result. My app stop works with white display when it did request. I read some info about that and I understood that call async method in not async method is bad, and it causes deadlock, but I don't know what to do with this. And I think deadlock causes that app stop works.
Here is method where app die:
public async Task<IEnumerable<RepositoryModel>> GetRepositoriesAsync()
{
try
{
var reposRequest = new RepositoryRequest { Sort = RepositorySort.FullName };
var gitHubRepos = await _gitHubClient.Repository.GetAllForCurrent(reposRequest); //async request, don't say about name convention, it is not my method.
var gitRemoteRepos = new List<RepositoryModel>();
foreach ( var repository in gitHubRepos )
{
var repos = new RepositoryModel();
repos.RepositoryTypeIcon = GetRepositoryTypeIcon(repository);
gitRemoteRepos.Add(repos);
}
return gitRemoteRepos;
}
catch ( WebException ex )
{
throw new Exception("Something wrong with internet connection, try to On Internet " + ex.Message);
}
catch ( Exception ex )
{
throw new Exception("Getting repos from github failed! " + ex.Message);
}
}
And here is viewmodel:
public class RepositoriesPageViewModel : BindableBase
{
private INavigationService _navigationService;
private readonly Session _session;
public ObservableCollection<RepositoryModel> Repositories { get; }
private readonly RepositoriesManager _repositoriesManager;
public RepositoriesPageViewModel(INavigationService navigationService, ISecuredDataProvider securedDataProvider)
{
_navigationService = navigationService;
var token = securedDataProvider.Retreive(ConstantsService.ProviderName, UserManager.GetLastUser());
_session = new Session(UserManager.GetLastUser(), token.Properties.First().Value);
var navigationParameters = new NavigationParameters { { "Session", _session } };
_repositoriesManager = new RepositoriesManager(_session);
var task = _repositoriesManager.GetRepositoriesAsync();
//task.Wait();
Repositories = task.Result as ObservableCollection<RepositoryModel>;
}
}
I recommend using my NotifyTask<T> type, which provides a data-bindable wrapper around Task<T>. I explain this pattern more completely in my article on async MVVM data binding.
public class RepositoriesPageViewModel : BindableBase
{
private INavigationService _navigationService;
private readonly Session _session;
public NotifyTask<ObservableCollection<RepositoryModel>> Repositories { get; }
private readonly RepositoriesManager _repositoriesManager;
public RepositoriesPageViewModel(INavigationService navigationService, ISecuredDataProvider securedDataProvider)
{
_navigationService = navigationService;
var token = securedDataProvider.Retreive(ConstantsService.ProviderName, UserManager.GetLastUser());
_session = new Session(UserManager.GetLastUser(), token.Properties.First().Value);
var navigationParameters = new NavigationParameters { { "Session", _session } };
_repositoriesManager = new RepositoriesManager(_session);
Repositories = NotifyTask.Create(GetRepositoriesAsync());
}
}
private async Task<ObservableCollection<RepositoryModel>> GetRepositoriesAsync()
{
return new ObservableCollection<RepositoryModel>(await _repositoriesManager.GetRepositoriesAsync());
}
Note that with this approach, your data binding would use Repositories.Result to access the actual collection. Other properties are also available, most notably Repositories.IsCompleted and Respositories.IsNotCompleted for showing/hiding busy spinners.