I'm getting started with simple signalR application. When user puts his "name" and "room". It is sent to my hub
//This is Index page
public async Task<ActionResult> OnPost()
{
UserConnection userConnection = new()
{
Name = UserInput,
Room = JoinRoomInput
};
await _hubConnection.SendAsync("JoinRoom", userConnection);
return RedirectToPage("ChatGroup");
}
my chat looks like this
public class ChatHub : Hub<IChatClient>
{
private readonly string _botUser;
public ChatHub()
{
_botUser = "MyChat Bot";
}
public async Task JoinRoom(UserConnection userConnection)
{
await Groups.AddToGroupAsync(Context.ConnectionId, userConnection.Room);
await Clients.Group(userConnection.Room).ReceiveMessage(_botUser, $"{userConnection.Name} has joined {userConnection.Room}");
}
public async Task SendMessage(UserConnection userConnection, string message)
{
await Clients.Group(userConnection.Room).ReceiveMessage($"{userConnection.Name} : ", message);
}
}
So then, the use posts his details and hit joinroom. He is redirected to another razor page called Chatgroup.In this page, He should get the message as defined in "JoinRoom" in ChatHub.
//This is Chatgroup page
private readonly HubConnection _hubConnection;
public List<string> ChatMessages { get; set; }
public ChatGroupModel(HubConnection hubConnection)
{
_hubConnection = hubConnection;
}
public void OnGet()
{
_hubConnection.On<string>("ReceiveMessage", (message) => //Breakpoint hits here
{
ChatMessages.Add(message); // When placed a breakpoint here, It is skipped.
});
}
I'm getting ChatMessages is Null error.
So my question is how will my client side code _hubConnection.On<> get the response from chathub?
I think this is the issue.
I've registered my signalr in startup class like this
services.AddTransient<HubConnection>((ChatClient) => {
var hubConnection = new HubConnectionBuilder().WithUrl("https://localhost:44389/chathub").Build();
hubConnection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await hubConnection.StartAsync();
};
hubConnection.StartAsync();
return hubConnection;
});
I get a null exception error in my razor page. I think it's because _hubConnection.On<>... skips chatmessage.add.
#foreach(var messages in Model.ChatMessages) //ChatMessages is Null
{
<div>
#messages
</div>
}
In my App, there is an Async Function ProcessOffer(). When I called it in Constructor as ProcessOffer, it works but synchronously. I want to call this Function in constructor asynchronously.
The ProcessOffer() is a function implemented in CredentialViewModel, but I want that, It should be triggered asynchronously everywhere on the App (IndexViewModel e.t.c).
If I'm on the IndexPage, and Web Application sends a request to Mobile Application, ProcessOffer(), should be triggered.. actually what ProcessOffer does is, that it asks the user to enter a PIN, if it's correct, it sends back a response to the Web Application.
I've tried answers from other Posts, but they returned the Error Autofac.Core.DependencyResolutionException: 'An exception was thrown while activating App.Name, when I sends a request to Mobile App from Web Application.
The Solutions I tried.
1- https://stackoverflow.com/a/64012442/14139029
2- Task.Run(() => ProcessOffer()).Wait();
3- ProcessOffer().GetAwatier().GetResult();
CredentialViewModel.cs
namespace Osma.Mobile.App.ViewModels.Credentials
{
public class CredentialViewModel : ABaseViewModel
{
private readonly CredentialRecord _credential;
private readonly ICredentialService _credentialService;
private readonly IAgentProvider _agentContextProvider;
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IPoolConfigurator _poolConfigurator;
[Obsolete]
public CredentialViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
ICredentialService credentialService,
IAgentProvider agentContextProvider,
IConnectionService connectionService,
IMessageService messageService,
IPoolConfigurator poolConfigurator,
CredentialRecord credential
) : base(
nameof(CredentialViewModel),
userDialogs,
navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
if (_credentialState == "Offered")
{
ProcessOffer();
}
}
[Obsolete]
public async Task ProcessOffer()
{
foreach (var item in _credential.CredentialAttributesValues)
{
await SecureStorage.SetAsync(item.Name.ToString(), item.Value.ToString());
}
var RegisteredPIN = await SecureStorage.GetAsync("RegisteredPIN");
string PIN = await App.Current.MainPage.DisplayPromptAsync("Enter PIN", null, "Ok", "Cancel", null, 6, Keyboard.Numeric);
if (PIN == RegisteredPIN)
{
try
{
//await _poolConfigurator.ConfigurePoolsAsync();
var agentContext = await _agentContextProvider.GetContextAsync();
var credentialRecord = await _credentialService.GetAsync(agentContext, _credential.Id);
var connectionId = credentialRecord.ConnectionId;
var connectionRecord = await _connectionService.GetAsync(agentContext, connectionId);
(var request, _) = await _credentialService.CreateRequestAsync(agentContext, _credential.Id);
await _messageService.SendAsync(agentContext.Wallet, request, connectionRecord);
await DialogService.AlertAsync("Request has been sent to the issuer.", "Success", "Ok");
}
catch (Exception e)
{
await DialogService.AlertAsync(e.Message, "Error", "Ok");
}
}
else if (PIN != RegisteredPIN && PIN != null)
{
DialogService.Alert("Provided PIN is not correct");
}
}
#region Bindable Command
[Obsolete]
public ICommand ProcessOfferCommand => new Command(async () => await ProcessOffer());
public ICommand NavigateBackCommand => new Command(async () =>
{
await NavigationService.PopModalAsync();
});
#endregion
#region Bindable Properties
private string _credentialState;
public string CredentialState
{
get => _credentialState;
set => this.RaiseAndSetIfChanged(ref _credentialState, value);
}
#endregion
}
}
IndexViewModel.cs
namespace Osma.Mobile.App.ViewModels.Index
{
public class IndexViewModel : ABaseViewModel
{
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IAgentProvider _agentContextProvider;
private readonly IEventAggregator _eventAggregator;
private readonly ILifetimeScope _scope;
public IndexViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
IConnectionService connectionService,
IMessageService messageService,
IAgentProvider agentContextProvider,
IEventAggregator eventAggregator,
ILifetimeScope scope
) : base(
"Index",
userDialogs,
navigationService
)
{
_connectionService = connectionService;
_messageService = messageService;
_agentContextProvider = agentContextProvider;
_eventAggregator = eventAggregator;
_scope = scope;
}
public override async Task InitializeAsync(object navigationData)
{
await base.InitializeAsync(navigationData);
}
public class Post
{
public string Success { get; set; }
public string firstname { get; set; }
}
[Obsolete]
public async Task ScanVerification(object sender, EventArgs e)
{
// Code
}
public async Task SettingsPage(SettingsViewModel settings) => await NavigationService.NavigateToAsync(settings, null, NavigationType.Modal);
#region Bindable Command
public ICommand SettingsPageCommand => new Command<SettingsViewModel>(async (settings) =>
{
await SettingsPage(settings);
});
[Obsolete]
public ICommand ScanVerificationCommand => new Command(async () => await ScanVerification(default, default));
#endregion
}
}
App.xml.cs
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Osma.Mobile.App
{
public partial class App : Application
{
public new static App Current => Application.Current as App;
public static IContainer Container { get; set; }
// Timer to check new messages in the configured mediator agent every 10sec
private readonly Timer timer;
private static IHost Host { get; set; }
public App()
{
InitializeComponent();
timer = new Timer
{
Enabled = false,
AutoReset = true,
Interval = TimeSpan.FromSeconds(10).TotalMilliseconds
};
timer.Elapsed += Timer_Elapsed;
}
public App(IHost host) : this() => Host = host;
public static IHostBuilder BuildHost(Assembly platformSpecific = null) =>
XamarinHost.CreateDefaultBuilder<App>()
.ConfigureServices((_, services) =>
{
services.AddAriesFramework(builder => builder.RegisterEdgeAgent(
options: options =>
{
options.AgentName = "Mobile Holder";
options.EndpointUri = "http://11.222.333.44:5000";
options.WalletConfiguration.StorageConfiguration =
new WalletConfiguration.WalletStorageConfiguration
{
Path = Path.Combine(
path1: FileSystem.AppDataDirectory,
path2: ".indy_client",
path3: "wallets")
};
options.WalletConfiguration.Id = "MobileWallet";
options.WalletCredentials.Key = "SecretWalletKey";
options.RevocationRegistryDirectory = Path.Combine(
path1: FileSystem.AppDataDirectory,
path2: ".indy_client",
path3: "tails");
// Available network configurations (see PoolConfigurator.cs):
options.PoolName = "sovrin-test";
},
delayProvisioning: true));
services.AddSingleton<IPoolConfigurator, PoolConfigurator>();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterAssemblyModules(typeof(CoreModule).Assembly);
if (platformSpecific != null)
{
containerBuilder.RegisterAssemblyModules(platformSpecific);
}
containerBuilder.Populate(services);
Container = containerBuilder.Build();
});
protected override async void OnStart()
{
await Host.StartAsync();
// View models and pages mappings
var _navigationService = Container.Resolve<INavigationService>();
_navigationService.AddPageViewModelBinding<MainViewModel, MainPage>();
_navigationService.AddPageViewModelBinding<RegisterViewModel, RegisterPage>();
_navigationService.AddPageViewModelBinding<IndexViewModel, IndexPage>();
_navigationService.AddPageViewModelBinding<SettingsViewModel, SettingsPage>();
_navigationService.AddPageViewModelBinding<CredentialsViewModel, CredentialsPage>();
_navigationService.AddPageViewModelBinding<CredentialViewModel, CredentialPage>();
if (Preferences.Get(AppConstant.LocalWalletProvisioned, false))
{
await _navigationService.NavigateToAsync<MainViewModel>();
}
else
{
await _navigationService.NavigateToAsync<ProviderViewModel>();
}
timer.Enabled = true;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
// Check for new messages with the mediator agent if successfully provisioned
if (Preferences.Get(AppConstant.LocalWalletProvisioned, false))
{
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var context = await Container.Resolve<IAgentProvider>().GetContextAsync();
await Container.Resolve<IEdgeClientService>().FetchInboxAsync(context);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
});
}
}
protected override void OnSleep() =>
// Stop timer when application goes to background
timer.Enabled = false;
protected override void OnResume() =>
// Resume timer when application comes in foreground
timer.Enabled = true;
}
}
Look, you have public override async Task InitializeAsync(object navigationData) method in the IndexViewModel class. I suppose the framework invoke it to initialize the view model. So, thus CredentialViewModel inherits same ABaseViewModel anyway, why wouldn't you just override InitializeAsync in your CredentialViewModel and call ProcessOffer from it?
public CredentialViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
ICredentialService credentialService,
IAgentProvider agentContextProvider,
IConnectionService connectionService,
IMessageService messageService,
IPoolConfigurator poolConfigurator,
CredentialRecord credential
) : base(
nameof(CredentialViewModel),
userDialogs,
navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
}
public override async Task InitializeAsync(object navigationData)
{
if (_credentialState != "Offered") return;
await ProcessOffer();
}
Anyway, you have to avoid calling asynchronous operations in constructors.
So in my View Models, if i need to initialize asynchronously I usually pass OnAppearing() to the view model instead of the constructor. Someone can tell me if this is unwise but it solved my needs.
Code Behind View:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CredentialView : ContentPage
{
public CredentialView()
{
InitializeComponent();
BindingContext = new CredentialViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
((CredentialViewModel)BindingContext).OnAppearing();
}
}
View Model:
public class CredentialViewModel : INotifyPropertyChanged
{
public CredentialViewModel ()
{
}
public async void OnAppearing()
{
await ProcessOffer();
}
}
Also just FYI, the biggest gotcha i've experienced with asynchronous code on xamarin is to use BeginInvokeOnMainThread. It's my understanding that touched UI should be executed on main thread! (probably why they call it the UI thread. haha)
Example:
Device.BeginInvokeOnMainThread(() =>
{
observableCollection.Clear();
});
As you've already asked before, kicking off async code in the constructor of a class can be a can of worms. You should instead consider doing either:
Starting ProcessOffer in a lifecycle method instead. For instance when the View is showing call ProcessOfferCommand.
Using fire and forget to not block the constructor: Task.Run(ProcessOffer) you should probably avoid this though.
Use something like NotifyTask Stephen Cleary describes here: https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/march/async-programming-patterns-for-asynchronous-mvvm-applications-data-binding you can find the complete code for it here: https://github.com/StephenCleary/Mvvm.Async/blob/master/src/Nito.Mvvm.Async/NotifyTask.cs
Use a CreateAsync pattern with a private constructor. However, this doesn't work well with Dependency Injection usually. Doing IO intensive work during resolution isn't really the correct place to do it.
In my opinion using either 1. or 3. would be the best solutions, perhaps leaning towards a combination of 1. using NotifyTask that can notify you when it is done loading.
I am trying to see Loading progress as follows, but it does not show up.
View.cs
ViewModel.SelectedCommand.Execute(null);
ViewModel.cs
public ICommand SelectedCommand
{
get
{
return new MvxAsyncCommand(async () =>
{
// the following does not show loading
using (UserDialogs.Instance.Loading("Loading..."))
{
var task = await _classroomService.GetClassRoomAsync(SelectedClassroom.Id);
ObservableCollection<ClassroomViewModel> class = new ObservableCollection<ClassroomViewModel>(task.ConvertAll(x => new ClassViewModel(x)));
}
});
}
}
Another example
public ICommand ReloadCommand
{
get
{
return new MvxAsyncCommand(async () =>
{
await RefreshList();
});
}
}
// the following also does not show loading
private async Task RefreshList()
{
using (UserDialogs.Instance.Loading("Loading..."))
{
var task = await _classService.GetClasses();
}
}
If you are using Acr.MvvmCross.Plugins.UserDialogs see that it's depreated and you should use directly Acr.UserDialogs.
Check if you have correctly initialized it as follows:
You have to register it in App.cs of your PCL project:
Mvx.RegisterSingleton<IUserDialogs>(() => UserDialogs.Instance);
And init from the android platform project in your main activity:
UserDialogs.Init(() => Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity)
Another thing to take into account is that you should inject it in your constructor as an IUserDialogs (you can use the static Instance way but it adds more flexibility and it is more testable by injecting it):
private readonly IUserDialogs _dialogs;
public ProgressViewModel(IUserDialogs dialogs)
{
this._dialogs = dialogs;
}
and use it like
private async Task RefreshList()
{
using (this._dialogs.Loading("Loading..."))
{
try
{
var task = await this._classService.GetClasses();
}
catch(Exception exc)
{
// This is done only for debugging to check if here lies the problem
throw exc;
}
}
}
You can check if it is properly working by calling something like
public ICommand MyTestCommand
{
get
{
return new MvxAsyncCommand(async () =>
{
// the following should should Loading for 3 seconds
using (this._dialogs.Loading("Loading..."))
{
await Task.Delay(TimeSpan.FromSeconds(3));
}
});
}
}
HIH
I dont like this approuch but it works
Device.BeginInvokeOnMainThread(async () =>
{
try
{
using (UserDialogs.Instance.Loading(("Loading...")))
{
await Task.Delay(300);
await _syncController.SyncData();
//Your Service code
}
}
catch (Exception ex)
{
var val = ex.Message;
UserDialogs.Instance.Alert("Test", val.ToString(), "Ok");
}
});
I have created a REST Api in ASP.Net web form. I can read and write data from MySql database using the API (I tested the Api using chrome's REST Client extension). Now I am trying to consume the Rest Api in my cross platform xamarin forms. I just added a button in my app and see whether it can retrieve data if I click the button. I inserted a breakpoint at the var Json to see whether I'm getting any data. But I am not able to retrieve it. I am running the Web Api in localhost. I run the app in VS Android emulator. Please guide me on how to properly consume the REST web service. Thank you.
Web Api
namespace WorkAppApi.Controllers
{
public class MachinesController : ApiController
{
// GET: api/Machines
public List<machines> Get()
{
DBConn db = new DBConn();
return db.getMachineList();
}
// GET: api/Machines/5
public machines Get(long id)
{
DBConn db = new DBConn();
machines m = db.getMachine(id);
return m;
}
// POST: api/Machines
public HttpResponseMessage Post([FromBody]machines value)
{
DBConn db = new DBConn();
long id;
id = db.addMachine(value);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created);
response.Headers.Location = new Uri(Request.RequestUri, String.Format("machines/{0}", id));
return response;
}
// PUT: api/Machines/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE: api/Machines/5
public void Delete(int id)
{
}
}
}
Function submit button.
namespace WorkApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage : ContentPage
{
private string Uri = "http://192.168.0.124:59547/api/Machines/";
public TestPage ()
{
InitializeComponent ();
check();
}
private async Task submit(object sender, EventArgs e)
{
var httpClient = new HttpClient();
var json = await httpClient.GetAsync(Uri);
}
}
}
this would be my answer:
namespace WorkApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage : ContentPage
{
private string Uri = "http://192.168.0.124:59547/api/";
List<Machine> machines;
public TestPage ()
{
InitializeComponent ();
check();
}
private async Task submit(object sender, EventArgs e)
{
var httpClient = new HttpClient();
httpClient.BaseAddress = Uri // I have changed the Uri variabele, you should extend this class and give it the same base address in the constructor.
var resp= await httpClient.GetAsync("Machines");
if (resp.Result.IsSuccessStatusCode)
{
var repStr = resp.Result.Content.ReadAsStringAsync();
machines= JsonConvert.DeserializeObject<List<Machine>>(repStr.Result.ToString());
}
}
}
}
i am trying to make an asynchronous call to a webservice.
I would like to make this call when opening the app (App.xaml.cs).
According to the answer that comes back to me, it has to navigate to a particular page
But I do not work.
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
try
{
CheckLogin().Wait();
}
catch (Exception e)
{
var t = e;
}
}
private static async Task CheckLogin()
{
try
{
var login = new Login
{
Email = "test#test.com",
Password = "test",
};
var client = new HttpClient { BaseAddress = new Uri("http://www.api.com/test/") };
var data = JsonConvert.SerializeObject(login);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PostAsync(#"api/it-IT/auth/token", content); //crash without error, freeze
if (response.IsSuccessStatusCode)
{
var successResult = JsonConvert.DeserializeObject<HttpResponseMessage>(response.Content.ReadAsStringAsync().Result);
if (successResult != null)
{
//return true;
}
else
{
//return false;
}
}
}
catch (Exception e)
{
var t = e;
}
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<NavigationPage>();
Container.RegisterTypeForNavigation<MainPage>();
Container.RegisterTypeForNavigation<MainPage2>();
Container.RegisterTypeForNavigation<MainPage3>();
}
}
When does the postasync call does not go more forward, not I get no errors, but does not proceed.
But if I try the same code in an application console, everything works fine, why?
class Program
{
static void Main(string[] args)
{
Console.WriteLine("A");
CheckLogin().Wait();
Console.WriteLine("K");
Console.ReadKey();
}
private static async Task CheckLogin()
{
try
{
var login = new Login
{
Email = "test#test.com",
Password = "#test",
};
var client = new HttpClient { BaseAddress = new Uri("http://www.api.com/test/") };
var data = JsonConvert.SerializeObject(login);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PostAsync(#"api/it-IT/auth/token", content);
if (response.IsSuccessStatusCode)
{
}
}
catch (Exception e)
{
var t = e;
}
}
}
If I try to do the same operation within a command with wait I do not work the same error, but if I do await, it will work fine, but in App.xaml.cs in OnInitialized() I can not put await
public DelegateCommand callCommand { get; set; }
public MainPage2ViewModel()
{
callCommand = new DelegateCommand(Call);
}
private void Call()
{
//await CheckLogin(); // work
CheckLogin().Wait(); // not work the same problem
var i = "pippo";
}
private async Task CheckLogin()
{
....
}
Is there anything to set with xamarin or with prism?
I've also the same strange error...
i fix with this workaround (use an async void that wrap async task)...
public App()
{
InitializeComponent();
Current.MainPage = new LoadingPage();
}
protected override void OnStart()
{
MagicInit();
base.OnStart();
}
public static async void MagicInit()
{
var f = await FileSystem.Current.LocalStorage.CreateFileAsync("db.sqlite", CreationCollisionOption.OpenIfExists);
DbConnection = f.Path;
await DataService.DbFill();
User = await DataService.Instance.Table<SpUser>().FirstOrDefaultAsync();
Current.MainPage = User != null ? (Page)new MainPage() : new LoginPage();
}