Caliburn Action not firing - caliburn.micro

<ItemsControl DockPanel.Dock="Right" x:Name="Actions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="Action"
HorizontalAlignment="Right"
Content="{Binding Label}"
Margin="3" Width="30"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The above view binds with this viewmodel
public class DeploymentInputViewModel<T> : PropertyChangedBase
{
public BindableCollection<InputActionViewModel> Actions {get;set;}
}
I see my buttons. But when clicking it nothing happen.
The viewModels for InputActionViewModel:
public abstract class InputActionViewModel{
public InputActionViewModel()
{
}
public virtual Task Action()
{
return Task.FromResult<object>(null);
}
public string ActionToolTip { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
and also
public class InputCertificateActionViewModel : InputActionViewModel
{
[Import]
private IShell _shell;
[Import]
private IWindowsDialogs _dialogs;
private readonly IDeploymentSettingInputViewModel vm;
public InputCertificateActionViewModel(IDeploymentSettingInputViewModel vm)
{
this.vm = vm;
Label = "...";
ActionToolTip = "Pick a Certificate";
}
public bool IsManagementCertificate {get;set;}
public bool IsDeploymentCertificate { get; set; }
public async override Task Action()
{
if(IsManagementCertificate)
{
var subs = await _shell.IdentityModel.GetEnabledSubscriptionsAsync();
foreach(var sub in subs)
{
using (ManagementClient client = CloudContext.Clients.CreateManagementClient(sub.GetCredentials()))
{
var cert = _dialogs.SelectItemDialog("Select a certificate", "Pick one", true,
(await client.ManagementCertificates.ListAsync()).Select(c =>
new SelectItem(c.Thumbprint, Encoding.Default.GetString(c.PublicKey), c, (s) => c.Thumbprint.Contains(s))).ToArray())
.Tag as ManagementCertificateListResponse.SubscriptionCertificate;
this.vm.Value = cert.Thumbprint;
}
}
}else if(IsDeploymentCertificate)
{
}
}
}
I am adding actionViewModels by inserting directly into the observable code at startup.
haveActions.Actions.Add(DI.BuildUp(new InputCertificateActionViewModel(vm)
{
IsDeploymentCertificate = certAttribute.IsDeploymentCertificate,
IsManagementCertificate = certAttribute.IsManagementCertificate,
}));
haveActions is an instance of InputCertificateActionViewModel

Couldn't fit this all in a comment:
I can't have a peek at the Caliburn.Micro at the moment, but it might be something related to calling your method Action.
At a guess though, I'd say that by convention Caliburn.Micro expects to find a method that matches the Action<T> delegate to use for it's Actions, so your public virtual Task Action() won't be located and bound.
Have a quick check by defining a new method with a compatible signature, e.g public void MyMethod() and checking to see if it's located correctly and will function.
If that is the problem, you'll probably want to have a look at the IResult and Coroutines part of the Caliburn.Micro documentation, which looks like it will help you implement your desired behaviour.

Related

Clear entry text from ViewModel using RelayCommand

I would like to clear entry text from my ViewModel which is binded there. In the code below I tried it by using a RelayCommand, but it doesn't work.
What i want to accomplish: When clicking button named AddQuestionToQuiz, a function is executed by using Command on the button. The function OnCreateQuizClick(), located in my ViewModel, is triggerd and this function needs to clear my entry text, which i don't get for the moment.
I also tried to use a regular Command instead of using a RelayCommand, but also here it doesn't want to work.
EDIT: UNDERNEATH CODE WORKS FINE - GOT UPDATED
Code is used to clear entry text when clicking on a button from your ViewModel, implementing INotifyPropertyChanged Interface
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}
EDIT: VIEWMODEL UPDATED
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}

How to get data from Web API to Xamarin forms Picker?

I'm developing a Xamarin APP and I want to load a Picker with data from a Web API that has Server Database. I tried to Google this but most of the articles don't show the content of source "Services class" that use Get async Method, Model class and ViewModel. I would be very grateful if someone could help me with an example.
This is my Controller in ASP.NET Web API
// GET: api/TipoUsers
public IQueryable<TipoUser> GetTipoUsers()
{
return db.TipoUsers;
}
Model class
public class TipoUsuario
{
public int IdTipoUsuario { get; set; }
public string Nome { get; set; }
}
ViewModel class
public class UsuarioViewModel
{
public ObservableCollection<TipoUsuario> tipos { get; set; }
public UsuarioViewModel() {
Task<List<TipoUsuario>> task = ApiService.ObterTipoUsuarios();
tipos = new ObservableCollection<TipoUsuario>(task.Result);
}
}
Xaml Page
<Picker Title="Selecione o Tipo de Usuario"
ItemsSource="{Binding tipos}"
ItemDisplayBinding="{Binding Nome}"/>
Xaml.cs
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class UsuarioPage : ContentPage
{
public UsuarioPage()
{
InitializeComponent();
BindingContext = new UsuarioViewModel();
}
}
}
Service class
public class ApiService
{
public const string Url = "http://thisismysite:44342/";
public static async Task<List<TipoUsuario>> GetTipoUsers()
{
try
{
HttpClient client = new HttpClient();
string url = Url + "/api/TipoUsers";
string response = await client.GetStringAsync(url);
List<TipoUsuario> tipos = JsonConvert.DeserializeObject<List<TipoUsuario>>(response);
return tipos;
}
catch (Exception)
{
throw;
}
}
}
when I debug the app it just doesn't load the screen.
This can happen for a few reasons, I would check your async method isn’t throwing an exception that you aren’t able to see. Async methods return a Task object and if an exception is thrown inside it will be visible in the returned object in Task.Exception.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library
Also property changed events aren’t called when you set an ObserableCollection to a new instance, you want to add and remove from the collection.
You want to change:
public UsuarioViewModel() {
Task<List<TipoUsuario>> task = ApiService.ObterTipoUsuarios();
tipos = new ObservableCollection<TipoUsuario>(task.Result);
}
to something like:
public UsuarioViewModel() {
Task<List<TipoUsuario>> task = ApiService.ObterTipoUsuarios();
var temptipos = task.Result;
foreach(var tipo in temptipos)
{
tipos.Add(tipo);
}
}

How send data (selectItem of listView) from xaml to ViewModel

I want get data selected in my viewModel
this is my xaml, but i donot know how solved my xaml, because this is bad, how use my behavior here in my xaml?
<ListView.Behaviors>
<behaviors:ItemTappedBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding SelectedTagChanged}" />
</behaviors:ItemTappedBehavior>
</ListView.Behaviors>
<ListView ItemsSource="{Binding actors}" ItemTapped="ListView_ItemTapped">
in my behindcode
public partial class ActorsView : ContentPage
{
public AccountsView()
{
InitializeComponent();
}
async void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
Actor selectedItem = (Actor)e.Item;
Console.WriteLine("WORK"+ Actor.Name);
}
but i want to get in my viewModel, dont in my behind code
i've seen to solved is with commands, or behaviors
this is my viewModel:
public class ActorsViewModel : ViewModelBase
{
public List<Actor> actors { get; set; }
public AccountsViewModel(IActorManager actorManager)
: base()
{
Edit, i am using commands but i dont know how use the answer from John Livermore, i want to use and show the console Console.WriteLine("ROW");.
public class ItemTappedBehavior : Behavior<ListView>
{
public ICommand Command { get; set; }
protected override void OnAttachedTo(ListView bindable)
{
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
}
public Command SelectedTagChanged
{
get
{
return new Command(row =>
{
Console.WriteLine("ROW");
});
}
}
}
Since you had asked for a simpler Behavior for ListView selection changed. Please use the following:
Following class is inherited from BehaviorBase which takes care of BindingContext allocation.
On the OnAttachedTo override the ItemSelected event is hooked.
On the OnDetachingFrom override the ItemSelected event is unhooked.
Command bindable property Binds to the Command in ViewModel
In the event the e.SelectedItem is passed to the Command. (Change
it to whatever value you wish to pass)
public class SelectionChangedEventBehavior : BehaviorBase<ListView>
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(SelectionChangedEventBehavior), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttachedTo(BindableObject bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject.ItemSelected += AssociatedObject_ItemSelected;
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
AssociatedObject.ItemSelected -= AssociatedObject_ItemSelected;
}
private void AssociatedObject_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (Command != null)
{
Command.Execute(e.SelectedItem);
}
}
}
Usage
Xaml
<ListView ItemsSource="{Binding Collection}">
<ListView.Behaviors>
<local:SelectionChangedEventBehavior Command="{Binding SelectionCommand}" />
</ListView.Behaviors>
</ListView>
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public Command<object> SelectionCommand { get; set; }
public ViewModel()
{
SelectionCommand = new Command<object>(OnSelection);
}
private void OnSelection(object item)
{
var selectedActor = (item as Actor);
}
}
Hope this could be useful for you. If possible use the EventToCommandBehavior as whole, just because you can reuse it for any control and any event.
In your XAML do the following:
Set your <ListView ItemsSource="{Binding actors}" ItemTapped="ListView_ItemTapped">
to be <ListView ItemsSource="{Binding actors}" SelectedItem = "{Binding SelectedActor, Mode=TwoWay}>
In your view model which your current XAML is bound to, create two new variables as so:
private Actor selectedActor;
public Actor SelectedActor {
get {return selectedActor;}
set {
selectedActor = value;
if(selectedActor != null) {//set properties here if needed}
OnPropertyChanged("SelectedActor"); //or however you raise your events, this is a
//custom method i've made
}
Now, where we define our actors item source, we will also define what a selectedActor is as so:
//get your actors, loop over them and add them to the list of actors
var actorItem = new Actor();
actors.Add(actorItem);
//outside of list raise the property changed event on your actors list.
OnPropertyChanged("actors")
That is a the pattern that i've used at work to get my selected item. If you then need to do something with it you could define it within the SelectedActor variable. For us our app is a hybrid between old xamarin (.Android and .iOS) and .Forms, so we pass Actions from the native level to the view model (ex: loading the summary page for the selected item)

Xamarin forms - Pass argument to the bindingcontext viewmodel specified in a xaml file

I have a xaml file with with an entry. I am binding the specific entry to a specific viewmodel. But the viewmodel expects a Navigator. How can I pass the navigator from the xaml file to the viewmodel?
<Entry Text="{Binding Signature, Mode=TwoWay}">
<Entry.BindingContext>
<vm:SignaturePopupViewModel>
// I need to pass a navigator..
</vm:SignaturePopupViewModel>
</Entry.BindingContext>
</Entry>
The viewmodel expects a navigation object. I use it to pop the page to go back to the previous page after running some code logic.
public SignaturePopupViewModel(INavigation navigation = null)
{
Navigation = navigation;
SendSignatureCommand = new Command(async () =>
{
await SendSignature();
await Navigation.PopAsync();
});
}
You do not need to use INavigation navigation in your SignaturePopupViewModel in your constructor to achieve the Navigation.
Just use a simple way is
await Application.Current.MainPage.Navigation.PopModalAsync(); Or
await Application.Current.MainPage.Navigation.PopAsync()
like following code.
public class SignaturePopupViewModel
{
public ICommand SendSignatureCommand { protected set; get; }
public SignaturePopupViewModel( )
{
SendSignatureCommand = new Command(async () =>
{
await SendSignature();
// if you use the MainPage = new NavigationPage( new MainPage()); in
//App.xaml.cs use following code.
await Application.Current.MainPage.Navigation.PopAsync();
// if not, just use await Application.Current.MainPage.Navigation.PopModalAsync();
});
}
}
Could you create an instance of the SignaturePopupVM in the ViewModel of that page and then bind the Text to that property instead?
VM:
SignaturePopupViewModel SignaturePopupVMInstance { get; private set; }
public ParentVM()//Constructor
{
SignaturePopupVMInstance = new SignaturePopupViewModel(new Navigator());
}
Xaml:
<Entry Text="{Binding SignaturePopupVMInstance.Signature, Mode=TwoWay}"/>
Edit:
public class TabPageVM{
public ChildVM TheVMForTabOne { get; set; }
public AnotherChildVM TheVMForTabTwo { get; set; }
public TabVM TheVMForTabThree { get; set; }
public TabPageVM(){
TheVMForTabOne = new ChildVM(/*parameters*/);
TheVMForTabTwo = new AnotherChildVM(/*parameters*/);
TheVMForTabThree = new TabVM(/*parameters*/);
}
}
Xaml for tabpage:
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Views="clr-namespace:App.ViewsForMyTabs"
x:Class="App.TabPageView"
BarBackgroundColor="#EEEEEE"
BarTextColor="Black"
BindingContext="{Binding TheTabbedPageVMInstance}">
<TabbedPage.Children>
<Views:TheViewForTabOne x:Name="TabOneView"
BindingContext="{Binding TheVMForTabOne}"/>
<Views:TheViewForTabTwo x:Name="TabTwoView"
BindingContext="{Binding TheVMforTabTwo}"/>
<Views:TheViewForTabThree x:Name="TabThreeView"
BindingContext="{Binding TheVMforTabThree}"/>
</TabbedPage.Children>
</TabbedPage>
Lets say TheViewForTabOne has the button on it that takes you to the new page. The VM for that view "TheVMForTabOne" would have something like this:
public class ChildVM{
public SignaturePopupViewModel SignaturePopupVMInstance { get; set; }
public Command NavigateToNewPageWithEntry { get; private set; }
public ChildVM(){
SignaturePopupVMInstance = new SignaturePopupViewModel(/*parameters*/);
NavigateToNewPageWithEntry = new Command(() =>{
//Navigate to new page with SignaturePopupVMInstance as the BindingContext
}
}
}
TheViewForTabOne
...
<Label Text="{Binding SignaturePopupVMInstance.Signature}"/>
<Button Command="{Binding NavigateToNewPageWithEntry}"/>
...

Asp Mvc 6 Model Validation with a service in custom ValidationAttribute

TLDR: In Asp Mvc 6 how do I perform model validation with a service using data annotations? What are the alternatives?
I have a very simple model
public class MyModel
{
[Required]
public string Name { get; set; }
}
I also have a service that exposes some simple validation methods
public interface IMyService
{
string[] ReservedWords { get; }
bool IsValidName(string name);
// Internally calls IsValidName and throws an Exception if the name is invalid
void Save(MyModel myModel);
// ... snip
}
And I have wired up my controller like so
public class MyController : Controller
{
private readonly IMyService _service;
public MyController(IMyService service)
{
_service = service;
}
// ... snip
public IActionResult Post(MyModel myModel)
{
if (!_service.IsValidName(input?.Name))
{
ModelState.AddModelError(nameof(MyModel.Name), "Invalid Name");
}
if (!ModelState.IsValid)
{
return View(myModel);
}
_service.Save(myModel);
return RedirectToAction(nameof(Index));
}
}
It feels a bit clucky to have 2 stages of validation - automatic model validation then manually performing service validation. I was hoping that something simialr to this would work
public class MyModel
{
[ServiceValidation(nameof(IMyService), nameof(IMyService.IsValidName)]
[Required]
public string Name { get; set; }
}
public ServiceValidationAttribute : ValidationAttribute
{
private readonly Type _interfaceOrClass;
private readonly string _methodOrProperty;
public ServiceValidationAttribute(Type interfaceOrClass, string methodOrProperty)
{
_interfaceOrClass = interfaceOrClass;
_methodOrProperty = methodOrProperty;
}
public override bool RequiresValidationContext => true;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var service = validationContext.GetService(_interfaceOrClass);
// Extension method in shared library to assist with reflection
bool isValid = _interfaceOrClass.ValueForMethodOrPropertyNamed<bool>(service, _methodOrProperty, value);
return isValid
? ValidationResult.Success
: new ValidationResult(ErrorMessage);
}
}
However var serivce is always null, is there any way around this? I have wired up the IMyService to an implementation in the Startup.cs as it is available in the Controller.
Alternatively is there a better way of adding to the ModelState with a service?

Resources