Xamarin Forms Passing Parameters Between ViewModels - c#

I have two viewmodels. The first SxCaseDetailViewModel contains the details for one SxCaseId. The second viewmodel CaseStaffJoinViewModel containes all of the staff related to the SxCaseId. I am not getting any related data in my CaseStaffJoinViewModel. How do I pass the int SxCaseId to the CaseStaffJoinViewModel so that it will show all related data?
ViewModel SxCaseDetailViewModel
public class SxCaseDetailViewModel : ViewModelBase
{
private ISxCaseDataService _sxCaseDataService;
private ICaseStaffJoinDataService _caseStaffJoinDataService;
private SxCase _selectedSxCase;
public SxCaseDetailViewModel(IConnectionService connectionService,
INavigationService navigationService, IDialogService dialogService,
ICaseStaffJoinDataService caseStaffJoinDataService,
ISxCaseDataService sxCaseDataService)
: base(connectionService, navigationService, dialogService)
{
_sxCaseDataService = sxCaseDataService;
_caseStaffJoinDataService = caseStaffJoinDataService;
}
public SxCase SelectedSxCase
{
get => _selectedSxCase;
set
{
_selectedSxCase = value;
OnPropertyChanged();
}
}
public override async Task InitializeAsync(object navigationData)
{
IsBusy = true;
SelectedSxCase = (SxCase) navigationData;
IsBusy = false;
}
public ICommand CaseStaffJoinTappedCommand => new Command<SxCase>(OnCaseStaffJoinTapped);
private void OnCaseStaffJoinTapped(SxCase selectedSxCase)
{
_navigationService.NavigateToAsync<CaseStaffJoinViewModel>(selectedSxCase);
}
}
ViewModel CaseStaffJoinViewModel
public class CaseStaffJoinViewModel : ViewModelBase
{
private ICaseStaffJoinDataService _caseStaffJoinDataService;
private ObservableCollection<CaseStaffJoin> _caseStaffJoins;
public CaseStaffJoinViewModel(IConnectionService connectionService,
INavigationService navigationService, IDialogService dialogService,
ICaseStaffJoinDataService caseStaffJoinDataService)
: base(connectionService, navigationService, dialogService)
{
_caseStaffJoinDataService = caseStaffJoinDataService;
}
public ObservableCollection<CaseStaffJoin> CaseStaffJoins
{
get => _caseStaffJoins;
set
{
_caseStaffJoins = value;
OnPropertyChanged();
}
}
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is int)
{
IsBusy = true;
// Get caseStaffJoins by id
CaseStaffJoins = (ObservableCollection<CaseStaffJoin>)
await _caseStaffJoinDataService.GetCaseStaffJoinsAsync((int) navigationData);
IsBusy = false;
}
else
{
}
}
}
Data Service
class CaseStaffJoinDataService :BaseService, ICaseStaffJoinDataService
{
HttpClient _client;
private readonly IGenericRepository _genericRepository;
public CaseStaffJoinDataService(IGenericRepository genericRepository,
IBlobCache cache = null) : base(cache)
{
_genericRepository = genericRepository;
_client = new HttpClient();
}
public async Task<ObservableCollection<CaseStaffJoin>> FilterAsync(int sxCaseId)
{
UriBuilder builder = new UriBuilder(ApiConstants.BaseApiUrl)
{
Path = $"{ApiConstants.CaseStaffsEndpoint}/{sxCaseId}"
};
IEnumerable<CaseStaffJoin> caseStaffJoins =
await _genericRepository.GetAsync<IEnumerable<CaseStaffJoin>>(builder.ToString());
return caseStaffJoins?.ToObservableCollection();
}
public async Task<CaseStaffJoin> GetCaseStaffJoinBySxCaseIdAsync(int sxCaseId)
{
UriBuilder builder = new UriBuilder(ApiConstants.BaseApiUrl)
{
Path = $"{ApiConstants.CaseStaffsEndpoint}/{sxCaseId}"
};
var caseStaffJoin = await _genericRepository.GetAsync<CaseStaffJoin>(builder.ToString());
return caseStaffJoin;
}
public async Task<IEnumerable<CaseStaffJoin>> GetCaseStaffJoinsAsync(int sxCaseId)
{
UriBuilder builder = new UriBuilder(ApiConstants.BaseApiUrl)
{
Path = $"{ApiConstants.CaseStaffsEndpoint}/{sxCaseId}"
};
var caseStaffJoins = await _genericRepository.GetAsync<List<CaseStaffJoin>>(builder.ToString());
return caseStaffJoins;
}

you are passing an SxCase in your navigation
private void OnCaseStaffJoinTapped(SxCase selectedSxCase)
{
_navigationService.NavigateToAsync<CaseStaffJoinViewModel>(selectedSxCase);
}
but you are only checking for an int when receiving the data in InitializeAsync
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is int)
the types need to match - you either need to pass an int or check for an SxCase

Related

async call in async void EventHandler leads to a deadlock

Is there a way to call SendAsync in OnConnect without leading to a deadlock? I'm not using .Wait or .Result and it still leads to a deadlock.
Edit:
The actual problem is that SendAsync is being called twice (once at OnConnect and once at Main). If I put a await Task.Delay(10000) before the second call in Main, it actually works good. How can I fix it? If there is no task delay, it basically hangs on await tcs.Task.ConfigureAwait(false), because it's being called twice and async void OnConnect is kinda "fire and forget", meaning that it's not waiting for the first SendAsync to complete, before it goes for the second call.
// Program.cs
var client = new Client(key, secret);
await client.StartAsync().ConfigureAwait(false);
await Task.Delay(3000); // This line fixes it, but it's kinda fake fix
await client.SendAsync(request).ConfigureAwait(false);
await client.SendAsync(request2).ConfigureAwait(false);
Console.ReadLine();
// Client.cs
public class Client
{
private static long _nextId;
private readonly WebSocketClient _webSocket;
private readonly ConcurrentDictionary<long, TaskCompletionSource<string>> _outstandingRequests = new();
...
public event EventHandler<ConnectEventArgs>? Connected;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public ValueTask StartAsync()
{
_client.Connected += OnConnect;
_client.MessageReceived += OnMessageReceived;
return _webSocket.StartAsync(); // there is a long-running `Task.Run` inside it, which keeps the web socket connection and its pipelines open.
}
private async void OnConnect(object? sender, ConnectEventArgs e)
{
await AuthAsync(...); // the problematic line
}
private void OnMessageReceived(object? sender, MessageReceivedEventArgs e)
{
... deserialization stuff
if (_requests.TryRemove(response.Id, out var tcs))
{
tcs.TrySetResult(message);
}
}
public ValueTask<TResponse?> SendAsync<TResponse>(JsonRpcRequest request)
{
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async ValueTask<TResponse?> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message).ConfigureAwait(false);
var response = await tcs.Task.ConfigureAwait(false); // it hangs here (deadlock)
return JsonSerializer.Deserialize<TResponse>(response);
}
}
public ValueTask<JsonRpcResponse?> AuthAsync(JsonRpcRequest request)
{
return SendAsync<JsonRpcResponse>(request);
}
private static long NextId()
{
return Interlocked.Increment(ref _nextId);
}
}
public sealed class WebSocketClient
{
private readonly AsyncManualResetEvent _sendSemaphore = new(false); // Nito.AsyncEx
private readonly WebSocketPipe _webSocket; // SignalR Web Socket Pipe
...
public event EventHandler<ConnectEventArgs>? Connected;
public event EventHandler<DisconnectEventArgs>? Disconnected;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public ValueTask StartAsync()
{
_ = Task.Run(async () =>
{
try
{
await CreatePolicy()
.ExecuteAsync(async () =>
{
await _webSocket.StartAsync(new Uri(_url), CancellationToken.None).ConfigureAwait(false);
Connected?.Invoke(this, new ConnectEventArgs());
_sendSemaphore.Set();
await ReceiveLoopAsync().ConfigureAwait(false);
})
.ConfigureAwait(false);
}
catch (Exception ex)
{
// Failed after all retries
Disconnected?.Invoke(this, new DisconnectEventArgs(ex));
}
});
return ValueTask.CompletedTask;
}
public async ValueTask SendAsync(string message)
{
await _sendSemaphore.WaitAsync().ConfigureAwait(false);
var encoded = Encoding.UTF8.GetBytes(message);
await _webSocket.Transport!.Output
.WriteAsync(new ArraySegment<byte>(encoded, 0, encoded.Length), CancellationToken.None)
.ConfigureAwait(false);
}
private IAsyncPolicy CreatePolicy()
{
var retryPolicy = Policy
.Handle<WebSocketException>()
.WaitAndRetryForeverAsync(_ => ReconnectInterval,
(exception, retryCount, calculatedWaitDuration) =>
{
_sendSemaphore.Reset();
Reconnecting?.Invoke(this, new ReconnectingEventArgs(exception, retryCount, calculatedWaitDuration));
return Task.CompletedTask;
});
return retryPolicy;
}
private async Task ReceiveLoopAsync()
{
while (true)
{
var result = await _webSocket.Transport!.Input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
var buffer = result.Buffer;
...
}
}
}
As mentioned in the comments, those web socket wrappers are using System.IO.Pipelines, which is incorrect. System.IO.Pipelines is a stream of bytes, so it's appropriate for (non-web) sockets; a web socket is a stream of messages, so something like System.Threading.Channels would be more appropriate.
You could try something like this, which I just typed up and haven't even run:
public sealed class ChannelWebSocket : IDisposable
{
private readonly WebSocket _webSocket;
private readonly Channel<Message> _input;
private readonly Channel<Message> _output;
public ChannelWebSocket(WebSocket webSocket, Options options)
{
_webSocket = webSocket;
_input = Channel.CreateBounded(new BoundedChannelOptions(options.InputCapacity)
{
FullMode = options.InputFullMode,
}, options.InputMessageDropped);
_output = Channel.CreateBounded(new BoundedChannelOptions(options.OutputCapacity)
{
FullMode = options.OutputFullMode,
}, options.OutputMessageDropped);
}
public ChannelReader<Message> Input => _input.Reader;
public ChannelWriter<Message> Output => _output.Writer;
public void Dispose() => _webSocket.Dispose();
public async void Start()
{
var inputTask = InputLoopAsync(default);
var outputTask = OutputLoopAsync(default);
var completedTask = await Task.WhenAny(inputTask, outputTask);
if (completedTask.Exception != null)
{
try { await _webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, statusDescription: null, default); } catch { /* ignore */ }
try { _input.Writer.Complete(completedTask.Exception); } catch { /* ignore */ }
try { _output.Writer.Complete(completedTask.Exception); } catch { /* ignore */ }
}
}
public sealed class Message
{
public WebSocketMessageType MessageType { get; set; }
public OwnedMemorySequence<byte> Payload { get; set; } = null!;
}
private async Task InputLoopAsync(CancellationToken cancellationToken)
{
while (true)
{
var payload = new OwnedMemorySequence<byte>();
var buffer = MemoryPool<byte>.Shared.Rent();
ValueWebSocketReceiveResult result;
do
{
result = await _webSocket.ReceiveAsync(buffer.Memory, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
_input.Writer.Complete();
return;
}
payload.Append(buffer.Slice(0, result.Count));
} while (!result.EndOfMessage);
await _input.Writer.WriteAsync(new Message
{
MessageType = result.MessageType,
Payload = payload,
}, cancellationToken);
}
}
private async Task OutputLoopAsync(CancellationToken cancellationToken)
{
await foreach (var message in _output.Reader.ReadAllAsync())
{
var sequence = message.Payload.ReadOnlySequence;
if (sequence.IsEmpty)
continue;
while (!sequence.IsSingleSegment)
{
await _webSocket.SendAsync(sequence.First, message.MessageType, endOfMessage: false, cancellationToken);
sequence = sequence.Slice(sequence.First.Length);
}
await _webSocket.SendAsync(sequence.First, message.MessageType, endOfMessage: true, cancellationToken);
message.Payload.Dispose();
}
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, statusDescription: null, cancellationToken);
}
public sealed class Options
{
public int InputCapacity { get; set; } = 16;
public BoundedChannelFullMode InputFullMode { get; set; } = BoundedChannelFullMode.Wait;
public Action<Message>? InputMessageDropped { get; set; }
public int OutputCapacity { get; set; } = 16;
public BoundedChannelFullMode OutputFullMode { get; set; } = BoundedChannelFullMode.Wait;
public Action<Message>? OutputMessageDropped { get; set; }
}
}
It uses this type for building a memory sequence:
public sealed class MemorySequence<T>
{
private MemorySegment? _head;
private MemorySegment? _tail;
public MemorySequence<T> Append(ReadOnlyMemory<T> buffer)
{
if (_tail == null)
_head = _tail = new MemorySegment(buffer, runningIndex: 0);
else
_tail = _tail.Append(buffer);
return this;
}
public ReadOnlySequence<T> ReadOnlySequence => CreateReadOnlySequence(0, _tail?.Memory.Length ?? 0);
public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex) =>
_tail == null ? new ReadOnlySequence<T>(Array.Empty<T>()) :
new ReadOnlySequence<T>(_head!, firstBufferStartIndex, _tail, lastBufferEndIndex);
private sealed class MemorySegment : ReadOnlySequenceSegment<T>
{
public MemorySegment(ReadOnlyMemory<T> memory, long runningIndex)
{
Memory = memory;
RunningIndex = runningIndex;
}
public MemorySegment Append(ReadOnlyMemory<T> nextMemory)
{
var next = new MemorySegment(nextMemory, RunningIndex + Memory.Length);
Next = next;
return next;
}
}
}
and this type for building an owned memory sequence:
public sealed class OwnedMemorySequence<T> : IDisposable
{
private readonly CollectionDisposable _disposable = new();
private readonly MemorySequence<T> _sequence = new();
public OwnedMemorySequence<T> Append(IMemoryOwner<T> memoryOwner)
{
_disposable.Add(memoryOwner);
_sequence.Append(memoryOwner.Memory);
return this;
}
public ReadOnlySequence<T> ReadOnlySequence => _sequence.ReadOnlySequence;
public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex) =>
_sequence.CreateReadOnlySequence(firstBufferStartIndex, lastBufferEndIndex);
public void Dispose() => _disposable.Dispose();
}
which depends on owned memory span extension methods I stole from here:
public static class MemoryOwnerSliceExtensions
{
public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start, int length)
{
if (start == 0 && length == owner.Memory.Length)
return owner;
return new SliceOwner<T>(owner, start, length);
}
public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start)
{
if (start == 0)
return owner;
return new SliceOwner<T>(owner, start);
}
private sealed class SliceOwner<T> : IMemoryOwner<T>
{
private readonly IMemoryOwner<T> _owner;
public Memory<T> Memory { get; }
public SliceOwner(IMemoryOwner<T> owner, int start, int length)
{
_owner = owner;
Memory = _owner.Memory.Slice(start, length);
}
public SliceOwner(IMemoryOwner<T> owner, int start)
{
_owner = owner;
Memory = _owner.Memory[start..];
}
public void Dispose() => _owner.Dispose();
}
}
This code is all completely untested; use at your own risk.

How can I check for a duplicate object in SQLite table in C#?

I am creating an application in Xamarin.Forms - it is an event management application - in the application the user can sign up to events and create events. When you create an event - you are given a code for that event. There is a window in which you can input the code and then it registers you to that event. So for example if someone wants to check who is coming to a birthday party they could put the code on the invitation and the people who receive the invitation could open the app - input the code - and say whether they are going or not and add some messages.
I followed this tutorial to implement SQLite into Xamarin.Forms:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
Here is the code:
public class LocalEventDatabase : ILocalEventDatabase
{
static readonly Lazy<SQLiteAsyncConnection> lazyInitializer = new Lazy<SQLiteAsyncConnection>(() =>
{
return new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
});
static SQLiteAsyncConnection Database => lazyInitializer.Value;
static bool initialized = false;
public LocalEventDatabase()
{
InitializeAsync().SafeFireAndForget(false, new Action<Exception>(async(Exception ex) =>
{
await Acr.UserDialogs.UserDialogs.Instance.AlertAsync(string.Format($"Don't panic! An exception has occurred: {ex.Message}"), "An exception has occurred", "OK");
}));
}
async Task InitializeAsync()
{
if (!initialized)
{
if (!Database.TableMappings.Any(m => m.MappedType.Name == typeof(Event).Name))
{
await Database.CreateTablesAsync(CreateFlags.None, typeof(Event)).ConfigureAwait(false); // not entirely understood what configure await is?
}
initialized = true;
}
}
public async Task<List<Event>> GetRegisteredEventsAsync()
{
return await Database.Table<Event>().ToListAsync();
}
public async Task<Event> GetEventAsync(Guid id)
{
return await Database.Table<Event>().Where(i => i.EventGuid == id).FirstOrDefaultAsync();
}
public async Task<int> InsertEventAsync(Event #event)
{
return await Database.InsertAsync(#event);
}
public async Task<int> DeleteEventAsync(Event #event)
{
return await Database.DeleteAsync(#event);
}
}
Here is the code behind for searching through the codes:
public class FindEventViewModel : ViewModelBase
{
private ObservableCollection<Event> _results;
private ObservableCollection<Event> _events;
public ICommand SearchCommand => new Command(OnSearchCommand);
public string EntryText { get; set; }
// FAB stands = 'floating action button'
public ObservableCollection<Event> Results
{
get => _results;
set
{
_results = value;
RaisePropertyChanged(nameof(Results));
}
}
public ObservableCollection<Event> Events
{
get => _events;
set
{
_events = value;
RaisePropertyChanged(nameof(Events));
}
}
public FindEventViewModel(IDialogService dialogService,
IEventService eventService,
IShellNavigationService shellNavigationService,
IThemeService themeService,
ILocalEventDatabase localEventDatabase) : base(dialogService, eventService, shellNavigationService, themeService, localEventDatabase)
{
}
public async Task<FindEventViewModel> OnAppearing()
{
await LoadData();
return this;
}
private async Task LoadData()
{
var data = await _eventService.GetAllEventsAsync();
Events = new ObservableCollection<Event>(data);
Results = new ObservableCollection<Event>();
}
public async void OnSearchCommand()
{
if (SearchEvents(EntryText).SearchResult == SearchResult.Match)
{
_dialogService.ShowAdvancedToast(string.Format($"Found matching event"), Color.Green, Color.White, TimeSpan.FromSeconds(2));
var eventFound = SearchEvents(EntryText).EventFound;
Results.Add(eventFound);
RaisePropertyChanged(nameof(eventFound));
// may be unneccessary to use it directly rather than with DI but in this case it is needed in order to evaluate what the user has pressed
var result = await UserDialogs.Instance.ConfirmAsync(new Acr.UserDialogs.ConfirmConfig()
{
Title = "Sign up?",
Message = string.Format($"Would you like to sign up to {eventFound.EventTitle}?"),
OkText = "Yes",
CancelText = "No",
});
if (result)
{
await _localEventDatabase.InsertEventAsync(eventFound);
_dialogService.ShowAdvancedToast(string.Format($"You signed up to {eventFound.EventTitle} successfully"), Color.CornflowerBlue, Color.White, TimeSpan.FromSeconds(2));
MessagingCenter.Send(eventFound as Event, MessagingCenterMessages.EVENT_SIGNEDUP);
}
}
else
{
_dialogService.ShowAdvancedToast(string.Format($"No event found matching query - retry?"), Color.Red, Color.White, TimeSpan.FromSeconds(2));
}
}
public EventSearchResult SearchEvents(string code)
{
foreach (Event _event in Events)
{
if (_event.EventCode == code)
{
return new EventSearchResult()
{
SearchResult = SearchResult.Match,
EventFound = _event,
};
}
}
return new EventSearchResult()
{
SearchResult = SearchResult.NoMatch,
};
}
}
The method returns a search result and the event found. One problem - the user can sign up to the same event twice. In the tutorial I followed it doesn't mention how to check for any duplicate items in the database table?

.Net core 3.1 return RedirectToPage("SomePage") not working as expecting

I am on the Dashboard page where I have inherited my base class, which is expecting redirection based on value. I actually don't want to display page of value is 0. Below is the code.
This is the main page I don't want to execute the OnGet() on the dashboard page. Before that, I have to check some values on HostBasePage
[Area("BusinessHost")]
[Authorize(Roles ="Host")]
public class dashboardModel : HostBasePage
{
public dashboardModel(IAccountBusinessRepository accountBusinessRepository, ISessionManager sessionManager)
: base(accountBusinessRepository, sessionManager) { }
public void OnGet()
{
}
}
This is my base class and I want to go to the "AccountSetup" page.
public class HostBasePage : PageModel
{
private readonly IAccountBusinessRepository _accountBusinessRepository;
private readonly ISessionManager _sessionManager;
public HostBasePage(IAccountBusinessRepository accountBusinessRepository, ISessionManager sessionManager)
{
_sessionManager = sessionManager;
_accountBusinessRepository = accountBusinessRepository;
ValidateAccount();
}
/// <summary>
/// Validate Host account.
/// Account setup and payment needs to me complated.
/// </summary>
private async Task<IActionResult> ValidateAccount()
{
try
{
var accountID = _sessionManager.GetSessionValue("AccountID");
HostProfileValidateModel obj = await _accountBusinessRepository.HostProfileValidate(Convert.ToInt32(accountID));
if (obj.IsAccountSetup == 0) {
return RedirectToPage("AccountSetup");
}
//if (obj.IsPaid == 0)
// throw new Exception("");
return
Page();
}
catch (Exception ex)
{
throw ex;
}
}
If obj.IsAccountSetup is 0, I need redirection. No error is comming right now, but it's not redirecting to the AccountSetup page.
This is the page I want to display.
[Area("BusinessHost")]
[Authorize(Roles = "Host")]
public class AccountSetupModel : PageModel
{
private readonly IAccountBusinessRepository _accountBusinessRepository;
private readonly IServiceBusinessRepository _serviceBusinessRepository;
private readonly ICategoryBusinessRepository _categoryBusinessRepository;
private readonly IFranchiseBusinessRepository _franchiseBusinessRepository;
private readonly ISessionManager _sessionManager;
private readonly IPlansBusinessRepository _plansBusinessRepository;
private readonly IBusinessHostAccountSetupRepository _businessHostAccountSetupRepository;
public AccountSetupModel(IBusinessHostAccountSetupRepository businessHostAccountSetupRepository, IAccountBusinessRepository accountBusinessRepository,IPlansBusinessRepository plansBusinessRepository , ISessionManager sessionManager, ICategoryBusinessRepository categoryBusinessRepository, IServiceBusinessRepository serviceBusinessRepository, IFranchiseBusinessRepository franchiseBusinessRepository)
{
_accountBusinessRepository = accountBusinessRepository;
_serviceBusinessRepository = serviceBusinessRepository;
_categoryBusinessRepository = categoryBusinessRepository;
_franchiseBusinessRepository = franchiseBusinessRepository;
_sessionManager = sessionManager;
_businessHostAccountSetupRepository = businessHostAccountSetupRepository;
_plansBusinessRepository = plansBusinessRepository;
}
public async void OnGet()
{
string email = _sessionManager.GetSessionValue("RegisterEmail");
ModelServiceModel = new ServicesModel();
ModelCategoryModel = new ServicesModel();
ModelFranchiseModel = new FranchiseModel();
GetAllAccountData();
GetAllCategories();
GetAllServices();
GetAllFranchise();
PlansModel = await _plansBusinessRepository.GetAllPlans();
if (PlansModel != null)
{
PlansModel.TotalAmmount = PlansModel.AccountSetupCharges + PlansModel.MonthlyCharges + PlansModel.FrontDeskCharges;
}
}
public void GetAllCategories()
{
var data = _categoryBusinessRepository.GetAllCategoriesList();
ModelServiceModel.Category = data.Result.Select(x => new SelectListItem
{
Text = Convert.ToString(x.CategoryName),
Value = Convert.ToString(x.CategoryID)
}).ToList();
}
}
You could just implement OnPageHandlerExecutionAsync methods in your PageModel like
public class HostBasePage : PageModel
{
private readonly IAccountBusinessRepository _accountBusinessRepository;
private readonly ISessionManager _sessionManager;
public HostBasePage(IAccountBusinessRepository accountBusinessRepository, ISessionManager sessionManager)
{
_sessionManager = sessionManager;
_accountBusinessRepository = accountBusinessRepository;
}
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
try
{
var accountID = _sessionManager.GetSessionValue("AccountID");
HostProfileValidateModel obj = await _accountBusinessRepository.HostProfileValidate(Convert.ToInt32(accountID));
if (obj.IsAccountSetup == 0)
{
context.Result = RedirectToPage("/AccountSetup", new { area = "BusinessHost" });
}
else
{
await next.Invoke();
}
}
catch (Exception ex)
{
throw ex;
}
}
}
Refer to https://learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-3.1#implement-razor-page-filters-by-overriding-filter-methods

MvvmCross - Passing a string with IMvxNavigationService

I'm currently working on a Xamarin.iOS project that uses a web-api to gather data. However, I'm running into some problems trying to pass the user input from a textfield to the Tableview that gets the result from the api.
To do this I've followed the example on the MvvmCross documentation.
The problem is that the input from the Textfield never reaches the 'Filter' property in my TableviewController's viewmodel. I think I'm not passing the string object correctly to my IMvxNavigationService when called.
To clarify, in my UserinputViewController I'm binding the textfield's text like so:
[MvxFromStoryboard(StoryboardName = "Main")]
public partial class SearchEventView : MvxViewController
{
public SearchEventView (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel> set = new MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel>(this);
set.Bind(btnSearch).To(vm => vm.SearchEventCommand);
set.Bind(txtSearchFilter).For(s => s.Text).To(vm => vm.SearchFilter);
set.Apply();
}
}
The Viewmodel linked to this ViewController looks like this:
public class SearchEventViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
private string _searchFilter;
public string SearchFilter
{
get { return _searchFilter; }
set { _searchFilter = value; RaisePropertyChanged(() => SearchFilter); }
}
public SearchEventViewModel(IMvxNavigationService mvxNavigationService)
{
this._navigationService = mvxNavigationService;
}
public IMvxCommand SearchEventCommand {
get {
return new MvxCommand<string>(SearchEvent);
}
}
private async void SearchEvent(string filter)
{
await _navigationService.Navigate<EventListViewModel, string>(filter);
}
}
And finally, TableviewController's viewmodel looks like this:
public class EventListViewModel : MvxViewModel<string>
{
private readonly ITicketMasterService _ticketMasterService;
private readonly IMvxNavigationService _navigationService;
private List<Event> _events;
public List<Event> Events
{
get { return _events; }
set { _events = value; RaisePropertyChanged(() => Events); }
}
private string _filter;
public string Filter
{
get { return _filter; }
set { _filter = value; RaisePropertyChanged(() => Filter); }
}
public EventListViewModel(ITicketMasterService ticketMasterService, IMvxNavigationService mvxNavigationService)
{
this._ticketMasterService = ticketMasterService;
this._navigationService = mvxNavigationService;
}
public IMvxCommand EventDetailCommand {
get {
return new MvxCommand<Event>(EventDetail);
}
}
private void EventDetail(Event detailEvent)
{
_navigationService.Navigate<EventDetailViewModel, Event>(detailEvent);
}
public override void Prepare(string parameter)
{
this.Filter = parameter;
}
public override async Task Initialize()
{
await base.Initialize();
//Do heavy work and data loading here
this.Events = await _ticketMasterService.GetEvents(Filter);
}
}
Whenever trying to run, the string object 'parameter' in my TableviewController's Prepare function remains 'null' and I have no idea how to fix it. Any help is greatly appreciated!
I believe the issue is with your command setup
new MvxCommand<string>(SearchEvent);
As this command is being bound to a standard UIButton. It will not pass through a parameter value of your filter but null instead. So the string parameter generic can be removed. Additionally, as you want to execute an asynchronous method I would suggest rather using MvxAsyncCommand
new MvxAsyncCommand(SearchEvent);
Then in terms of SearchEvent method you can remove the parameter. The value of filter is bound to your SearchFilter property. It is this property's value that you want to send as the navigation parameter.
private async Task SearchEvent()
{
await _navigationService.Navigate<EventListViewModel, string>(SearchFilter);
}

Register event on object injected through constructor (PropertyChanged is always null)

Is it possible to have an object injected through the constructor and register an event on it? I am currently trying to implement something like this and my PropertyChanged even is always null. I think it has to do with the fact that I am not instantiating the object in my class where I register the event, but I am not quite sure as I am still new to event driven code.
Anyway here is the relevant code:
Injected via dependency:
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, section.Key, file);
});
}
}
public interface IWritableOptions<out T> : IOptionsSnapshot<T> where T : class, new()
{
void Update(Action<T> applyChanges);
event PropertyChangedEventHandler PropertyChanged;
}
public class WritableOptions<T> : INotifyPropertyChanged, IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
string section,
string file)
{
_environment = environment;
_options = options;
_section = section;
_file = file;
}
public T Value
{
get
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
return sectionObject;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
OnPropertyChanged("Value");
}
}
Class consuming the injected dependency:
public class ClientService : HostedService
{
public ClientService(IWritableOptions<ClientSettings> clients)
{
clients.PropertyChanged += PropertyChanged;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
}
EDIT: To further clarify, I all use this in a .Net Core WebAPI project. I register ClientService like this:
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureWritable<ClientSettings>(Configuration.GetSection("ClientSettings"));
services.AddSingleton<IHostedService, ClientService>();
services.AddMvc();
}
I then have a controller with which I can update these settings:
private IWritableOptions<ClientSettings> _clients;
public ClientController(IWritableOptions<ClientSettings> clients)
{
_clients = clients;
}
[HttpPost]
[Route(CoreConfigClientRoutes.UpdateRoute)]
public void UpdateClients([FromBody] IEnumerable<ModuleSetting> updatedClients)
{
var clients = _clients.Value.Clients.ToList();
foreach (var updatedClient in updatedClients)
{
var index = clients.IndexOf(clients.Where(c => c.Id == updatedClient.Id).First());
clients[index] = updatedClient;
}
_clients.Update(c => c.Clients = clients.ToArray());
}
Sources: IHostedService, IWritableOptions
Am I missing something here or is this not possible?
Working GitHub Repo: EventDI
I was getting above in the WPF app.
You probably shouldn't use code below if you care. However, for a quick 'patch and move on' I did the following.
I castrated the above solution since it was failing with dependency injection bits. Just manually described where to get appsettings.json and removed the failing to bind components.
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
public WritableOptions(
IOptionsMonitor<T> options,
string section,
string file)
{
_options = options;
_section = section;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var physicalPath = Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.FullName+ "\\appsettings.json";
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.SetAttributes(physicalPath, FileAttributes.Normal);
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}

Categories

Resources