INotifyDataErrorInfo reusable methods - viewmodel

I am using MVVM-Light and for every view model I have to create an implementation of the INotifyDataErrorInfo which in some cases uses same methods to validate same property types.
In this example I am using DateTime:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using SL.Resources;
namespace SL.ViewModel
{
public partial class AdministrationViewModel : INotifyDataErrorInfo
{
#region Validations
public void isDateToValid(DateTime? value, DateTime? dateFrom, string propertyName)
{
//check if null
if (value == null) AddError(propertyName, CommonErrors.DateNull_ERROR, false);
else RemoveError(propertyName, CommonErrors.DateNull_ERROR);
//check if min and max value
if (value < DateTime.MinValue || value > DateTime.MaxValue) AddError(propertyName, CommonErrors.DateNotValid_ERROR, false);
else RemoveError(propertyName, CommonErrors.DateNotValid_ERROR);
if (value < dateFrom) AddError(propertyName, CommonErrors.DateFromSmall_ERROR, false);
else RemoveError(propertyName, CommonErrors.DateFromSmall_ERROR);
}
public void IsDateValid(DateTime? value, string propertyName)
{
if (value == null) AddError(propertyName, CommonErrors.DateNull_ERROR, false);
else RemoveError(propertyName, CommonErrors.DateNull_ERROR);
if (value < DateTime.MinValue || value > DateTime.MaxValue) AddError(propertyName, CommonErrors.DateNotValid_ERROR, false);
else RemoveError(propertyName, CommonErrors.DateNotValid_ERROR);
}
#endregion
#region INotifyDataErrorInfo Members
public Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if isWarning is
// false. Raises the ErrorsChanged event if the collection changes.
public void AddError(string propertyName, string error, bool isWarning)
{
if (!errors.ContainsKey(propertyName))
errors[propertyName] = new List<string>();
if (!errors[propertyName].Contains(error))
{
if (isWarning) errors[propertyName].Add(error);
else errors[propertyName].Insert(0, error);
RaiseErrorsChanged(propertyName);
}
}
// Removes the specified error from the errors collection if it is
// present. Raises the ErrorsChanged event if the collection changes.
public void RemoveError(string propertyName, string error)
{
if (errors.ContainsKey(propertyName) &&
errors[propertyName].Contains(error))
{
errors[propertyName].Remove(error);
if (errors[propertyName].Count == 0) errors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
}
public void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public event System.EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
!errors.ContainsKey(propertyName)) return null;
return errors[propertyName];
}
public bool HasErrors
{
get { return errors.Count > 0; }
}
#endregion
}
}
How could I make this code reusable in other view models, so I don't have to implement the same thing over and over again?
I created a class that implements INotifyDataErrorInfo :
public class ViewModelValidation : INotifyDataErrorInfo
But when I want to use it in my view model it doesn't work:
public partial class AdministrationViewModel : ViewModelValidation
Error:
Partial declarations of 'SL.ViewModel.AdministrationViewModel' must not specify different base classes...
this is because in my main view model file I have a base class from MVVM-Light:
public partial class AdministrationViewModel : ViewModelBase
Any help resolving this is appretiated.

I figured it out myself. I created a ViewModelCommon class based on ViewModelBase from MVVM-Light and added the INotifyDataErrorInfo interface to it:
public class ViewModelCommon : ViewModelBase, INotifyDataErrorInfo
Then in my view model code instead of ViewModelBase I just used my ViewModelCommon class:
public partial class AdministrationViewModel : ViewModelCommon
And it works just fine.

Related

Access custom attributes of .NET class inside custom json converter

In my project, I have written a custom json converter to trim the white-spaces present in the string property.
Here is an example of the typical class we will use,
public class Candidate
{
public string CandidateName { get; set; }
}
Here is my custom json converter
public class StringSanitizingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue , JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
string sanitizedString = (reader.Value as string).Trim();
if (StringSanitizeOptions.HasFlag(StringSanitizeOptions.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var text = (string)value;
if (text == null)
writer.WriteNull();
else
writer.WriteValue(text.Trim());
}
}
With my custom converter I am now able to format the string by trimming any white-spaces present sent to the action methods using my 'Candidate' as one of its parameter.
public void Post(ComplexType complexTypeParameter){
}
Everything worked well so far. I later wanted to enhance this json converter to format the string properties based on the attributes set to the string property in the Candidate class. for example, assume I have written my candidate class like this,
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
}
And if I wanted to format the string properties of a class based on the custom attribute configuration inside the json converter , I am not able to access this custom attribute and its configuration inside the ReadJson method of the custom converter.
Here is what I have tried so far but with no luck,
Not present in the CustomAttributes property of the objectType
parameter sent to the ReadJson() method.
Was trying to see if I could extract the parent class of the property inside the ReadJson() method, so that I could apply reflection on the class to extract the custom attributes given to any of its property,but I could not extract that too.
The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().
Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.
First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
[StringSanitizingOptions(Option.DoNotTrim)]
public string StringLiteral { get; set; }
public string DefaultString { get; set; }
public List<string> DefaultStrings { get; set; }
}
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
public Option StringSanitizeOptions { get; set; }
public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
{
this.StringSanitizeOptions = stringSanitizeOptions;
}
}
[Flags]
public enum Option
{
Default = 0,
ToLowerCase = (1<<0),
DoNotTrim = (1<<1),
}
public static class StringSanitizeOptionsExtensions
{
public static bool HasFlag(this Option options, Option flag)
{
return (options & flag) == flag;
}
}
public class StringSanitizingConverter : JsonConverter
{
readonly Option options;
public StringSanitizingConverter() : this(Option.Default) { }
public StringSanitizingConverter(Option options)
{
this.options = options;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
var sanitizedString = (reader.Value as string);
if (!options.HasFlag(Option.DoNotTrim))
sanitizedString = sanitizedString.Trim();
if (options.HasFlag(Option.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called with null
var text = (string)value;
if (!options.HasFlag(Option.DoNotTrim))
text = text.Trim();
writer.WriteValue(text);
}
}
Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():
public static class JsonContractExtensions
{
public static JsonContract AddStringConverters(this JsonContract contract)
{
if (contract is JsonPrimitiveContract)
{
if (contract.UnderlyingType == typeof(string))
contract.Converter = new StringSanitizingConverter();
}
else if (contract is JsonObjectContract)
{
var objectContract = (JsonObjectContract)contract;
foreach (var property in objectContract.Properties)
{
if (property.PropertyType == typeof(string))
{
var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
.Cast<StringSanitizingOptionsAttribute>()
.SingleOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
}
}
}
}
return contract;
}
}
public class ConfigurableContractResolver : DefaultContractResolver
{
// This contract resolver taken from the answer to
// https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
// https://stackoverflow.com/a/46083201/3744182
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Then, finally you can deserialize and serialize Candidate as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
}.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};
var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);
var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);
Notes:
I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:
Simplicity.
A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).
Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.
JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.
You may want to cache the contract resolver for best performance.
To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.
Sample working .Net fiddle here.

PropertyChanged is triggered, but the view is not updated

I'm changing the label in the class constructor and it works fine, the label is updated ("0"). I'm also trying to update the label when I click in a button, but it's not working ("X"). I noticed debugging that the label value is updated, PropertyChanged is triggered, but the view doesn't change.
public class HomeViewModel : ViewModelBase
{
string playerA;
public string PlayerA
{
get
{
return playerA;
}
set
{
playerA = value;
this.Notify("playerA");
}
}
public ICommand PlayerA_Plus_Command
{
get;
set;
}
public HomeViewModel()
{
this.PlayerA_Plus_Command = new Command(this.PlayerA_Plus);
this.PlayerA = "0";
}
public void PlayerA_Plus()
{
this.PlayerA = "X";
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The name of the parameter passed in your PropertyChangedEventArgs is wrong. You are using "playerA" but the name of the (public) property is "PlayerA" (uppercase "P"). Change this.Notify("playerA"); to this.Notify("PlayerA"); or even better:
Notify(nameof(PlayerA));
You can completely get rid of passing the name of the param by adding a [CallerMemberName] attribute to the Notify() method.
protected void Notify([CallerMemberName] string propertyName = null)
This allows you to just call Notify() without parameters and the name of the changed property will automatically be used.

UICollectionViewLayoutAttributes custom derived class instance is not created

I have created this class
public class CustomLayoutAttributes: UICollectionViewLayoutAttributes
{
public float PhotoHeight { get; set; }
public override NSObject Copy (NSZone zone)
{
CustomLayoutAttributes copy = base.Copy(zone) as CustomLayoutAttributes;
copy.PhotoHeight = PhotoHeight;
return copy;
}
public override bool IsEqual (NSObject anObject)
{
CustomLayoutAttributes attributes = anObject as CustomLayoutAttributes;
if (attributes != null) {
if (attributes.PhotoHeight == PhotoHeight) {
return base.IsEqual (anObject);
}
}
return false;
}
public CustomLayoutAttributes (IntPtr ptr) : base(ptr)
{
}
}
And in my CustomCollectionViewLayout PrepareLayout method I try to create an instance but always get null.
[Register("CustomCollectionViewLayout")]
public class CustomCollectionViewLayout : UICollectionViewLayout
{
public override void PrepareLayout ()
{
// stuff...
CustomLayoutAttributes attributes = CustomLayoutAttributes.CreateForCell(indexPath) as CustomLayoutAttributes;
if (attributes != null) {
// Never gets in here, always null
}
// stuff...
}
}
I have applied the same login in Swift iOS and it works perfect.
Need to use the generic version of CreateForCell:
UICollectionViewLayout.CreateForCell<CustomLayoutAttributes>(indexPath);
This is because C# doesn't have virtual class methods like Objective-C does so it can't tell in CreateForCell which class you called it on unless you tell it with a type argument.

Xamarin.Android: item previously inserted in ArrayAdapter is not found again

I've inherited this Xamarin.Android app and it has a few issues.
A particular bug involves an ArrayAdapter<ProductListObject>, where ProductListObject is a common POCO that's shared between subprojects (i.e. Android, Windows Phone and iOS); it just has a couple of properties (e.g. an Id) and overrides the (.NET) Equals() method to achieve structural equality:
public class ProductListObject
{
public long Id { get; set; }
public override bool Equals(object obj)
{
if (!(obj is ProductListObject))
{
return false;
}
return Id == (obj as ProductListObject).Id;
}
}
The problem is that whenever I put an instance of this ProductListObject in an ArrayAdapter, I can't find it again, even if they have the same Id:
var p1 = new ProductListObject { Id = 1 };
var p2 = new ProductListObject { Id = 1 };
var areEqual = p1.Equals(p2); // returns True, as expected
var productAdapter = new ArrayAdapter<ProductListObject>(this, 0, new[] { p1 });
var position = productAdapter.GetPosition(p2); // returns -1 >:(
My question is: what do I have to do to make my POCO's work with Xamarin.Android types that rely on the Java equals() method, internally (like ArrayAdapter; which delegates to List.indexOf(Object))?
What I have tried:
verified that the corresponding Java version works as expected (it does)
overrode GetHashCode() (it doesn't matter, as I expected)
googled and checked the Xamarin documentation for information about implementing Equals() (I found nothing particularly relevant)
Thanks,
Jan
I did as Matt R suggested and created a proxy that inherits from Java.Lang.Object and delegates to the actual .NET object:
public class JavaObject<TValue> : Java.Lang.Object
{
public readonly TValue Value;
internal JavaObject(TValue value)
{
Value = value;
}
public override bool Equals(Java.Lang.Object that)
{
if (!(that is JavaObject<TValue>))
{
return false;
}
return Value.Equals((that as JavaObject<TValue>).Value);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}
This doesn't tie my platform-agnostic POCO's to the Android implementation, plus it doesn't force me to lock into some rigid inheritance tree, which is always a plus.
Application is straightforward:
var p1 = new JavaObject<ProductListObject>(new ProductListObject { Id = 1 });
var p2 = new JavaObject<ProductListObject>(new ProductListObject { Id = 1 });
var areEqual = p1.Equals(p2); // returns True, as expected
var productAdapter = new ArrayAdapter<JavaObject<ProductListObject>>(this, 0, new[] { p1 });
var position = productAdapter.GetPosition(p2); // returns 0!
It looks like .NET objects get wrapped within a Java.Lang.Object when used inside a Android.Widget.ArrayAdapter. Therefore, the comparison method that's used in the productAdapter.GetPosition(...) call is actually the java Equals(Java.Lang.Object o) method for the wrapping Java.Lang.Object.
To make a ProductListObject resolve to the same index when two objects have the same Id, make ProductListObject derive from Java.Lang.Object, override the Equals(Java.Lang.Object) and forward it to the .NET Equals(System.Object) method:
public class ProductListObject : Java.Lang.Object
{
public long Id { get; set; }
public override bool Equals(object obj) // Inherited from System.Object.
{
if (!(obj is ProductListObject))
{
return false;
}
return Id == (obj as ProductListObject).Id;
}
public override bool Equals (Java.Lang.Object o) // Inherited from Java.Lang.Object.
{
return this.Equals (o as System.Object);
}
}
If you can't inherit ProductListObject from Java.Lang.Object, another option is to implement your own proxy class:
public class ProductListObject
{
public long Id { get; set; }
public override bool Equals(System.Object obj)
{
if (!(obj is ProductListObject))
{
return false;
}
return Id == (obj as ProductListObject).Id;
}
}
public class JavaProxy: Java.Lang.Object
{
public Object Object { get; private set; }
public JavaProxy(System.Object o)
{
Object = o;
}
public override bool Equals (Java.Lang.Object o)
{
var proxy = o as JavaProxy;
if (o != null) {
return Object.Equals (proxy.Object);
}
return base.Equals (o);
}
}
// ...
var productAdapter = new ArrayAdapter<JavaProxy>(this, 0, new[] { new JavaProxy(p1) });
var position = productAdapter.GetPosition(new JavaProxy(p2));
It's not as clean as the first approach but it also works.

Linq executing query generates not supported exception

I'm trying to execute a linq query in an extension method but I am getting the following exception on the ToArray() function call - it seems the query is having issues with my IList somehow, I have tried many different things and googled but fails to see the issue
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: Cannot compare elements of type 'System.Collections.Generic.IList`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'. Only primitive types, enumeration types and entity types are supported.
Code
public static IList<Shared.Poco.DimValue> ToDimValuePocoList(this IQueryable<DAL.DimValue> source)
{
IList<Shared.Poco.DimValue> values = new List<Shared.Poco.DimValue>();
foreach (DAL.DimValue dv in source.ToArray())
{
values.Add(new Shared.Poco.DimValue
{
DimensionId = dv.DimensionID,
DimValueName = dv.DimValueName,
DimValueNo = dv.DimValueNo
});
}
return values;
}
The class the calls the extension looks like this:
public class DimValue : IDimValue
{
private IDimValueRepository _repository;
private IAccessableDimensionValues _accessableDimensionValues;
private IRevision _revision;
public DimValue(IDimValueRepository reposotory, IAccessableDimensionValues accessableDimensionValues, IRevision revision)
{
_repository = reposotory;
_accessableDimensionValues = accessableDimensionValues;
_revision = revision;
}
public IList<Shared.Poco.DimValue> GetDimValueList(int budgetId, Shared.Poco.Dimension dimension, IList<string> dimensionValues, Shared.Poco.User user)
{
IList<Shared.Poco.DimValue> values = new List<Shared.Poco.DimValue>();
int budgetRevisionId = _revision.GetLatesRevision(budgetId);
IList<string> uniqueAccessableUBLValues = _accessableDimensionValues.GetUniqueAccessableDimensionValues(dimension, user.UserId, budgetRevisionId);
if (dimensionValues != null && dimensionValues.Count > 0)
{
uniqueAccessableUBLValues = dimensionValues;
}
return _repository.GetDimValueList(budgetId, dimension.ToString(), uniqueAccessableUBLValues, user).ToDimValuePocoList();
}
The implementation of the injected repository class inherits from the following base repository class
public interface IRepository<T>
{
void Insert(T entity);
void Delete(T entity);
IQueryable<T> SearchFor(Expression<Func<T, bool>> predicate);
IQueryable<T> GetAll();
T GetById(int id);
}
public class Repository<T> : IRepository<T> where T : class
{
protected DbSet<T> DbSet;
public Repository(DbContext dataContext)
{
DbSet = dataContext.Set<T>();
}
#region IRepository<T> Members
public void Insert(T entity)
{
DbSet.Add(entity);
}
public void Delete(T entity)
{
DbSet.Remove(entity);
}
public IQueryable<T> SearchFor(Expression<Func<T, bool>> predicate)
{
return DbSet.Where(predicate);
}
public IQueryable<T> GetAll()
{
return DbSet;
}
public T GetById(int id)
{
return DbSet.Find(id);
}
#endregion
}
Repository implementation
public class DimValueRepository : Repository<DimValue>, IDimValueRepository
{
public DimValueRepository(KonstruktEntities context) : base(context) { }
public IQueryable<DimValue> GetDimValueList(int budgetId, string dimensionId, IList<string> dimensionFilter, Shared.Poco.User user)
{
return this.SearchFor(dv => dv.BudgetID == budgetId && dv.DimensionID == dimensionId &&
(dimensionFilter == null || dimensionFilter.Count == 0 || dimensionFilter.Contains(dv.DimValueNo)));
}
public IQueryable<DimValue> GetDimValueList(Shared.Poco.Dimension dimension, string startsWith, Shared.Poco.User user)
{
return this.SearchFor(dv=>dv.DimensionID == dimension.ToString() &&
dv.DimValueNo.StartsWith(startsWith));
}
}
EDIT
It's failing on the following row in the ToDimValuePocoList function:
foreach (DAL.DimValue dv in source.ToArray())
Here is the row calling the extension in the DimValue class
return _repository.GetDimValueList(budgetId, dimension.ToString(), uniqueAccessableUBLValues, user).ToDimValuePocoList();
Answered by #Will in the comment above, i.e. cannot use dimensionFilter in my predicate. So I moved that logic outside of the predicate.

Resources