I am new to MvvmCross, I have a question with regards to binding in Android. I can bind to single property but unable to data bind to an object. Not sure what I am doing wrong but here it is:
Model class:
public class Login : MvxNotifyPropertyChanged
{
private string _email;
public string Email
{
get { return _email; }
set
{
SetProperty(ref _email, value);
}
}
public string Password { get; set; }
}
Snippet of View Model Class:
public class LoginOptionViewModel: MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public LoginOptionViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
LoginCommand =
new MvxAsyncCommand(async () => await _navigationService.Navigate<RegistrationViewModel>());
}
public IMvxAsyncCommand LoginCommand { get; set; }
private Login _loginInfo;
public Login LoginInfo
{
get
{
return _loginInfo ?? new Login();
}
set
{
_loginInfo = value;
RaisePropertyChanged(() => LoginInfo);
}
}
}
Snippet of Android Axml:
<EditText
android:id="#+id/loginEmailTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/EmailHint"
android:textColor="#color/darkNavy"
android:inputType="textEmailAddress"
local:MvxBind="Text LoginInfo.Email" />
Where am I going wrong, I have placed a breakpoint but do not see it being hit. This is binded to EditText but nothing appears to happen. Am I missing or doing something wrong in order to bind to an object property ?
MvvmCross object data binding
You could implement the MvxNotifyPropertyChanged interface, so the system could notifies clients that a property value has changed.
Modify your object to :
public class Login : MvxNotifyPropertyChanged
{
private string _email;
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
}
In the MainViewModel :
private Login _login;
public Login Login
{
get
{
return _login ?? new Login() { Email = "=-="};
}
set
{
_login = value;
RaisePropertyChanged(() => Login);
}
}
Then use it in axml :
local:MvxBind="Text Login.Email"
It works fine on my side.
Update :
I cant reproduce your problem, but here is my complete code, hope this can help you :
public class MainViewModel : MvxViewModel
{
public MainViewModel()
{
}
public override Task Initialize()
{
return base.Initialize();
}
public IMvxCommand ResetTextCommand => new MvxCommand(ResetText);
private void ResetText()
{
Text = "Hello MvvmCross";
}
private string _text = "Hello MvvmCross";
public string Text
{
get { return _text; }
set { SetProperty(ref _text, value); }
}
private Login _login;
public Login Login
{
get
{
return _login ?? new Login() { Email = "=-="};
}
set
{
_login = value;
RaisePropertyChanged(() => Login);
}
}
}
public class Login : MvxNotifyPropertyChanged
{
private string _email;
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
}
Effect.
Related
I’m working on upgrading our application vaadin version from 7.7.24 to 8.13.3. We’ve completed all the dependency issues and i’m able to start the application in locally.
We have a textbox that is showing up the Event data.
Here is the class file that i'm using:
#Entity
#Table(name = "changelog")
public class ChangelogEvent extends BaseEntity
{
#Column(name = "remote_ip")
private String remoteIp;
#Column(name = "remote_host")
private String remoteHost;
#Column(name = "event")
#Enumerated(EnumType.ORDINAL)
private ChangelogEventType eventType;
#Column(name = "entity_type")
private String entityType;
public ChangelogEvent()
{
}
public ChangelogEvent(String remoteIp, String remoteHost, ChangelogEventType eventType)
{
this.remoteIp = remoteIp;
this.remoteHost = remoteHost;
this.eventType = eventType;
}
public String getRemoteIp()
{
return remoteIp;
}
public void setRemoteIp(String remoteIp)
{
this.remoteIp = remoteIp;
}
public ChangelogEventType getEventType()
{
return eventType;
}
public void setEventType(ChangelogEventType eventType)
{
this.eventType = eventType;
}
public String getRemoteHost()
{
return remoteHost;
}
public void setRemoteHost(String remoteHost)
{
this.remoteHost = remoteHost;
}
public String getEntityType()
{
return entityType;
}
public void setEntityType(String entityType)
{
this.entityType = entityType;
}
}
And here is my ChangelogEventType.java file that defined ChangelogEventType enum:
public enum ChangelogEventType
{
CREATED("Created"),
UPDATED("Updated"),
DELETED("Deleted"),
LOGIN("Login"),
LOGOUT("Logout"),
LOGIN_RETRY("Login Retry"),
ACCOUNT_LOCKED("Account Locked"),
PASSWORD_EXPIRED("Password Expired"),
PASSWORD_CHANGED("Password Changed");
private String text;
ChangelogEventType(String text)
{
this.text = text;
}
public String getText()
{
return text;
}
public static ChangelogEventType fromString(String text)
{
if (text != null)
{
for (ChangelogEventType event : ChangelogEventType.values())
{
if (text.equalsIgnoreCase(event.text))
{
return event;
}
}
}
return null;
}
}
Here is the code that i'm using for binding the values into textfield.
eventType = createTextField("Event", COLUMN_WIDTH);
binder.forField(eventType)
.withNullRepresentation("None")
.bind(ChangelogEvent::getEventType, ChangelogEvent::setEventType);
Is there any way to bind the Enum to textbox ?
You need to write custom converter and use it in Binder using withConverter method, in your case something like:
class StringToChangelogEventTypeConverter implements Converter<String, ChangelogEventType> {
#Override
public Result<ChangelogEventType> convertToModel(String fieldValue, ValueContext context) {
// Produces a converted value or an error
ChangelogEventType event = ChangelogEventType.fromString(fieldValue);
if (event != null) {
// ok is a static helper method that creates a Result
return Result.ok(ChangelogEventType.fromString(fieldValue));
} else {
// error is a static helper method that creates a Result
return Result.error("Please enter a number");
}
}
#Override
public String convertToPresentation(ChangelogEventType event, ValueContext context) {
// Converting to the field type should always succeed,
// so there is no support for returning an error Result.
return event.getText();
}
}
Recently, I developed a component , using factory pattern. However, I did a research. on how to improve it using TypedFactoryFacility, since we are using Castle.WIndsor.
Can you please provide a simple complete example? I have read few of them but still can't really fully understand . SO far, my code looks like that :
public class DynamoStoreService : IDynamoStoreService
{
private IDynamoStoreFactory _dynamoStoreFactory;
public DynamoStoreService(IDynamoStoreFactory dynamoStoreFactory)
{
_dynamoStoreFactory=dynamoStoreFactory;
}
public IDynamoStore GetProductDataDynamoStore(string storageAccount)
{
return _dynamoStoreFactory.Create(storageAccount);
}
}
public class DynamoStoreFactory : IDynamoStoreFactory
{
private IStorageAccountSelector _storageAccountSelector;
public DynamoStoreFactory(IStorageAccountSelector storageAccountSelector)
{
_storageAccountSelector = storageAccountSelector;
}
public IDynamoStore Create(string storageAccount)
{
return new AzureKeyValueStore(_storageAccountSelector.GetCredentials(storageAccount).StorageAccount, "pointerfiles");
}
}
public class StorageAccountSelector : IStorageAccountSelector
{
private readonly IConfigurationSettings _settings;
public StorageAccountSelector(IConfigurationSettings settings)
{
_settings = settings;
}
BlobCredentials IStorageAccountSelector.GetCredentials(string storageAccount)
{
return new BlobCredentials()
{
Container = string.Empty,
StorageAccount = GetStorageAccount(storageAccount)
};
}
private string GetStorageAccount(string storageAccount)
{
switch (storageAccount)
{
case "CustomerPolarisingCategoryBlobStorageAccountKey":
return _settings.CustomerPolarisingCategoryBlobStorageAccount;
case "CustomerPolarisingSegmentBlobStorageAccountKey":
return _settings.CustomerPolarisingSegmentBlobStorageAccount;
case "P2ProductSimilarityBlobStorageAccountKey":
return _settings.P2ProductSimilarityBlobStorageAccount;
case "ProductPolarisingCategoryBlobStorageAccountKey":
return _settings.ProductPolarisingCategoryBlobStorageAccount;
case "ProductPolarisingSegmentBlobStorageAccountKey":
return _settings.ProductPolarisingSegmentBlobStorageAccount;
case "SignalBlobStorageAccountKey":
return _settings.SignalBlobStorageAccount;
}
return string.Empty;
}
}
}
So basically, the IDynamostore , whenvever called, we need to be able to pass a different connection string. I have figured out the above design.. could this be improved using TypedFactoryFacility?
Thanks
Maybe the code below can give you an idea about how to use the TypedFactoryFacility. If you have studied it and have questions about it, please let me know.
Kind regards,
Marwijn.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace ConsoleApplication3
{
public class TypedFactoryComponentSelector : DefaultTypedFactoryComponentSelector
{
private readonly StorageAccountSelector _storageAccountSelector;
public TypedFactoryComponentSelector(StorageAccountSelector storageAccountSelector)
{
_storageAccountSelector = storageAccountSelector;
}
protected override System.Collections.IDictionary GetArguments(MethodInfo method, object[] arguments)
{
var dictionary = new Dictionary<string, object>();
dictionary.Add("mappedStorageAccount", _storageAccountSelector.GetCredentials((string)arguments[0]).StorageAccount);
dictionary.Add("files", "pointerfiles");
return dictionary;
}
}
public interface IDynamoStore
{
}
public class AzureKeyValueStore : IDynamoStore
{
public AzureKeyValueStore(string mappedStorageAccount, string files)
{
Console.WriteLine(mappedStorageAccount);
Console.WriteLine(files);
}
}
public class BlobCredentials
{
public string Container { get; set; }
public string StorageAccount { get; set; }
}
public interface IDynamoStoreFactory
{
IDynamoStore Create(string storageAccount);
}
public class StorageAccountSelector
{
public BlobCredentials GetCredentials(string storageAccount)
{
return new BlobCredentials()
{
Container = string.Empty,
StorageAccount = GetStorageAccount(storageAccount)
};
}
public string GetStorageAccount(string storageAccount)
{
return storageAccount + "Mapped";
return string.Empty;
}
}
class Program
{
static void Main(string[] args)
{
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<IDynamoStoreFactory>().AsFactory(new TypedFactoryComponentSelector(new StorageAccountSelector())),
Component.For<IDynamoStore>().ImplementedBy<AzureKeyValueStore>()
);
var factory = container.Resolve<IDynamoStoreFactory>();
factory.Create("storageAccount");
}
}
}
I am trying to building custom validation with dynamic message in fluent validation library.
For example :
public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
public CreateProcessValidator()
{
RuleFor(x => x.ProcessFile).Must((x,e) => IsProcessFileValid(x.ProcessFile))).WithMessage("Parse failed with error : {0}");
}
public bool IsProcessFileValid(HttpPostedFileBase file)
{
var errorMessage = "..." // pass result to validaton message ?
// logic
return false;
}
}
Is here any workaround how to pass validation result ?
Thanks
Have you tried something like this?
public class IsProcessFileValid : PropertyValidator
{
public IsProcessFileValid(): base("{ValidationMessage}") {}
protected override IsValid(PropertyValidatorContext context)
{
if (!IsProcessFileValid1(context))
context.MessageFormatter.AppendArgument("ValidationMessage",
"Custom validation message #1");
if (!IsProcessFileValid2(context))
context.MessageFormatter.AppendArgument("ValidationMessage",
"Custom validation message #2");
// ...etc
return true;
}
private bool IsProcessFileValid1(PropertyValidatorContext context)
{
// logic
return false;
}
private bool IsProcessFileValid2(PropertyValidatorContext context)
{
// logic
return false;
}
// ...etc
}
With extension method:
public static class IsProcessFileValidExtensions
{
public static IRuleBuilderOptions<T, object> MustBeValidProcessFile<T>
(this IRuleBuilder<T, object> ruleBuilder)
{
return ruleBuilder.SetValidator(new IsProcessFileValid());
}
}
... and then use it without a custom WithMessage:
public CreateProcessValidator()
{
RuleFor(x => x.ProcessFile).MustBeValidProcessFile();
}
By creating a custom PropertyValidator, you can encapsulate the default validation message within that class and make it dynamic. However you must not use the .WithMessage extension when declaring the RuleFor, because that would override the default validation message which you customized directly inside the PropertyValidator.
There's no way to do that. I would split the complex validation method you currently have into smaller methods (IsProcessFileValid1, IsProcessFileValid2, IsProcessFileValid3, ...) so that you could have more fine grained control over the error message. Also each method will be responsible for validating only once concern making them more reusable (single responsibility):
RuleFor(x => x.ProcessFile)
.Must(IsProcessFileValid1)
.WithMessage("Message 1")
.Must(IsProcessFileValid2)
.WithMessage("Message 2")
.Must(IsProcessFileValid3)
.WithMessage("Message 3");
Also notice how I simplified the lambda as the method could directly be passed to Must as argument.
Here is how I solved it. Tested with FluentValidation v8.5.0
class EmptyValidationMessage : IStringSource
{
public string ResourceName => null;
public Type ResourceType => null;
public string GetString(IValidationContext context)
{
return string.Empty;
}
public static readonly EmptyValidationMessage Instance = new EmptyValidationMessage();
}
public class MyPropValidator : PropertyValidator
{
public MyPropValidator() : base(EmptyValidationMessage.Instance)
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
// if not valid
Options.ErrorMessageSource = new StaticStringSource("my message");
// you can do LanguageStringSource, LazyStringSource, LocalizedStringSource, etc
// example with localized string (https://github.com/clearwaterstream/LocalizedString.FluentValidation)
Options.ErrorMessageSource = new LocalizedStringSource("my message").InFrench("moi message");
return false;
}
}
Faced the same issue, while trying to insert exception message into WithMessage().
It worked with the method overload taking Func<T, string> messageProvider as parameter.
Here is the solution presented on the posters example (working code, FluentValidation v 9.1):
public class CreateProcessVM
{
public object ProcessFile { get; set; }
}
public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
public CreateProcessValidator()
{
var message = "Something went wrong.";
RuleFor(x => x.ProcessFile)
.Must((x, e) => IsProcessFileValid(x.ProcessFile, out message))
// .WithMessage(message); will NOT work
.WithMessage(x => message); //Func<CreateProcessVM, string> as parameter
}
public bool IsProcessFileValid(object file, out string errorMessage)
{
errorMessage = string.Empty;
try
{
Validate(file);
return true;
}
catch (InvalidOperationException e)
{
errorMessage = e.Message;
return false;
}
}
private void Validate(object file)
{
throw new InvalidOperationException("File of type .custom is not allowed.");
}
}
And a test demonstrating that we really get the exception message in the error message:
[Fact]
public void Test()
{
var validator = new CreateProcessValidator();
var result = validator.Validate(new CreateProcessVM());
Assert.False(result.IsValid);
Assert.Equal("File of type .custom is not allowed.", result.Errors[0].ErrorMessage);
}
When I add the HttpHeader filter, the other filters stop working. Im not sure why.
BaseApiController.cs
[ExceptionHandling, ApiValidation, HttpHeader("X-Robots-Tag", "noindex, nofollow")]
public abstract class BaseApiController : System.Web.Http.ApiController
{
HttpHeaderFilter.cs
namespace Tournaments.Models.Mvc.Filters
{
public class HttpHeaderAttribute : System.Web.Http.Filters.FilterAttribute
{
public string Name { get; set; }
public string Value { get; set; }
public HttpHeaderAttribute(string name, string value)
{
Name = name;
Value = value;
}
public override bool AllowMultiple
{
get { return true; }
}
}
public class HttpHeaderFilter : System.Web.Http.Filters.IActionFilter
{
private readonly string _name;
private readonly string _value;
public HttpHeaderFilter(string name, string value)
{
_name = name;
_value = value;
}
public bool AllowMultiple
{
get { return true; }
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
return continuation().ContinueWith<HttpResponseMessage>((tsk) =>
{
HttpResponseMessage response = tsk.Result;
response.Headers.Add(_name, _value);
return response;
}, TaskContinuationOptions.OnlyOnRanToCompletion);;
}
}
}
Global.asax
protected override IKernel CreateKernel()
{
return Bootstrap.Configure((kernel) =>
{
kernel.Bind<IUnitOfWork>().To<UnitOfWork>();
kernel.Bind<IDatabaseFactory>().To<DatabaseFactory>().InRequestScope();
//kernel.Bind<IDatabaseFactory>().To<DatabaseFactory>().InScope(q => HttpContext.Current ?? StandardScopeCallbacks.Thread(q));
kernel.Bind<IEmailService>().To<EmailService>().WithConstructorArgument("templates", emailTemplates);
kernel.Bind<IPaymentMethod>().To<AuthorizeNetProvider>().Named("AuthorizeNet");
kernel.Bind<IPaymentMethod>().To<StripeProvider>().Named("Stripe");
kernel.Bind<List<IPaymentMethod>>().ToConstant(kernel.GetAll<IPaymentMethod>().ToList());
ServiceBindings.Register(kernel);
RepositoryBindings.Register(kernel);
ValidationBindings.Register(kernel);
kernel.BindHttpFilter<ApiValidationFilter>(System.Web.Http.Filters.FilterScope.Action)
.WhenControllerHas<ApiValidationAttribute>();
kernel.BindHttpFilter<HttpHeaderFilter>(System.Web.Http.Filters.FilterScope.Controller)
.WhenControllerHas<HttpHeaderAttribute>()
.WithConstructorArgumentFromControllerAttribute<HttpHeaderAttribute>("name", q => q.Name)
.WithConstructorArgumentFromControllerAttribute<HttpHeaderAttribute>("value", q => q.Value);
GlobalConfiguration.Configuration.Filters.Add(new ExceptionHandlingAttribute());
GlobalConfiguration.Configuration.DependencyResolver = new Common.Ioc.NinjectDependencyResolver(kernel);
GlobalConfiguration.Configuration.Formatters.Add(new CustomXmlFormatter());
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
});
}
I added this
TaskContinuationOptions.ExecuteSynchronously
Instead of
TaskContinuationOptions.OnlyOnRanToCompletion
and it works fine now.
Hello I have an error 500 (internal server error) when I run the code below. My issue is that I have no trace at all of the error. It seems that visual studio is unable to catch it.
The following code returns a Candidate if I try to add pers to candidate the code fail and i get error 500. The thing is PersonAddressDescription implement AddressDescription is inheritance the problem ?
public class CheckController : ApiController
{
public Candidate Get()
{
PersonAddressDescription pers = new PersonAddressDescription();
Candidate candidate = new Candidate();
//IF I REMOVE THIS NO PROBLEM
candidate.address = pers;
return candidate;
}
}
AddressDescription class
/// <remarks/>
[System.Xml.Serialization.XmlIncludeAttribute(typeof(CompanyAddressDescription))]
[System.Xml.Serialization.XmlIncludeAttribute(typeof(PersonAddressDescription))]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.17626")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.crif-online.ch/webservices/crifsoapservice/v1.00")]
public abstract partial class AddressDescription : object, System.ComponentModel.INotifyPropertyChanged {
private Location locationField;
private ContactItem[] contactItemsField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public Location location {
get {
return this.locationField;
}
set {
this.locationField = value;
this.RaisePropertyChanged("location");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("contactItems", Order=1)]
public ContactItem[] contactItems {
get {
return this.contactItemsField;
}
set {
this.contactItemsField = value;
this.RaisePropertyChanged("contactItems");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
PersonAddressDescription class that implement AddressDescription
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.17626")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.crif-online.ch/webservices/crifsoapservice/v1.00")]
public partial class PersonAddressDescription : AddressDescription {
private string firstNameField;
private string lastNameField;
private string maidenNameField;
private Sex sexField;
private bool sexFieldSpecified;
private string birthDateField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public string firstName {
get {
return this.firstNameField;
}
set {
this.firstNameField = value;
this.RaisePropertyChanged("firstName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public string lastName {
get {
return this.lastNameField;
}
set {
this.lastNameField = value;
this.RaisePropertyChanged("lastName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=2)]
public string maidenName {
get {
return this.maidenNameField;
}
set {
this.maidenNameField = value;
this.RaisePropertyChanged("maidenName");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=3)]
public Sex sex {
get {
return this.sexField;
}
set {
this.sexField = value;
this.RaisePropertyChanged("sex");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool sexSpecified {
get {
return this.sexFieldSpecified;
}
set {
this.sexFieldSpecified = value;
this.RaisePropertyChanged("sexSpecified");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=4)]
public string birthDate {
get {
return this.birthDateField;
}
set {
this.birthDateField = value;
this.RaisePropertyChanged("birthDate");
}
}
}
I suspect that the object you retrieved (addResp) contains circular references somewhere in its object graph. Circular references cannot be JSON serialized.
For example try putting the following code inside your controller to test what happens when you attempt to JSON serialize this instance:
TypeIdentifyAddressResponse addResp = ws.identifyAddress("test");
string json = JsonConvert.SerializeObject(addResp);
UPDATE:
It seems that AddressDescription is an abstract class and your actual instance is PersonAddressDescription. You need to indicate that to the serializer by using the [KnownType] attribute:
[KnownType(typeof(PersonAddressDescription))]
[KnownType(typeof(CompanyAddressDescription))]
...
public abstract partial class AddressDescription : object, System.ComponentModel.INotifyPropertyChanged {
{
...
}
As an alternative if you don't want to further pollute your (already polluted) domain models with other attributes you could also define the known type inside your WebApiConfig.cs:
config.Formatters.XmlFormatter.SetSerializer<Candidate>(
new DataContractSerializer(typeof(Candidate),
new Type[] { typeof(PersonAddressDescription) }));