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

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.

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.

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.

Default model example in Swashbuckle (Swagger)

I'm running ASP WebAPI 2 and successfully installed Swashbuckle. I am trying to figure out how one defines what the default schema values are?
For example, on the Swagger live demo site they changed the default value of pet to "doggie". They also defined the allowable values for status. (Live Demo)
I managed to get this working by following what's on this link:
https://github.com/domaindrivendev/Swashbuckle/issues/69#issuecomment-53953785
In short this is what needs to be done:
Create the classes SwaggerDefaultValue and AddDefaultValues as described in the link. Some changes that I did:
public class SwaggerDefaultValue : Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string value)
{
this.Value = value;
}
public SwaggerDefaultValue(string name, string value) : this(value)
{
this.Name = name;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
private IDictionary<string, object> GetParameterValuePairs(HttpActionDescriptor actionDescriptor)
{
IDictionary<string, object> parameterValuePairs = new Dictionary<string, object>();
foreach (SwaggerDefaultValue defaultValue in actionDescriptor.GetCustomAttributes<SwaggerDefaultValue>())
{
parameterValuePairs.Add(defaultValue.Name, defaultValue.Value);
}
foreach (var parameter in actionDescriptor.GetParameters())
{
if (!parameter.ParameterType.IsPrimitive)
{
foreach (PropertyInfo property in parameter.ParameterType.GetProperties())
{
var defaultValue = GetDefaultValue(property);
if (defaultValue != null)
{
parameterValuePairs.Add(property.Name, defaultValue);
}
}
}
}
return parameterValuePairs;
}
private static object GetDefaultValue(PropertyInfo property)
{
var customAttribute = property.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
return customAttribute.Value;
}
return null;
}
}
Edit your SwaggerConfig and add the AddDefaultValues class to the OperationFilters:
GlobalConfiguration.Configuration
.EnableSwagger(c => {
...
c.OperationFilter<AddDefaultValues>()
...
});
Now for the parameters I want default values I just add the following:
public IHttpActionResult Put([FromBody]Pet pet)
{
...
return Ok();
}
public class Pet {
[SwaggerDefaultValue("doggie")]
public string Name { get; set; }
[SwaggerDefaultValue("available")]
public string Status;
...
}
Well the code of vgaspar.trivix did not work completly for me, the default values did not get set for the schema. Also i got an NullPointerException. I managed to get it working as intended by editing the Apply method and manipulated the schemaRegistry like this:
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
return;
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
if (param.schema != null && param.schema.#ref != null)
{
string schemaName = param.schema.#ref.Split('/').LastOrDefault();
if (schemaRegistry.Definitions.ContainsKey(schemaName))
foreach (var props in schemaRegistry.Definitions[schemaName].properties)
{
if (parameterValuePairs.ContainsKey(props.Key))
props.Value.#default = parameterValuePairs[props.Key];
}
}
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
An example Model Schema can be defined by implementing ISchemaFilter and registering it using the following:
httpConfig
.EnableSwagger(c =>
{
c.SchemaFilter<AddSchemaExamples>()
});
An example implementation is provided here:
public class AddSchemaExamples : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (type == typeof(Product))
{
schema.example = new Product
{
Id = 123,
Type = ProductType.Book,
Description = "Treasure Island",
UnitPrice = 10.0M
};
}
}
}
Source: https://github.com/domaindrivendev/Swashbuckle/issues/162
I know this thread is quite old, but I wanted to share my solution which creates a custom constructor just for the Swagger example schema.
In my model:
/// <summary>
/// Supply a custom constructor for Swagger where you can apply defaults to control the example schema.
/// The constructor must have one parameter of type System.Reflection.ParameterInfo[].
/// Note: Setting a property to null will prevent it from showing in the Swagger example.
/// </summary>System.Reflection.ParameterInfo[].
/// </summary>
public class SwaggerConstructor : Attribute { }
In SwaggerConfig.cs:
c.SchemaFilter<ApplySchemaVendorExtensions>();
The schema extension:
public class ApplySchemaVendorExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault(c => c.GetCustomAttribute<SwaggerConstructor>() != null);
if (constructor != null)
{
schema.example = constructor.Invoke(new object[] { constructor.GetParameters() });
}
}
}
Usage:
[SwaggerConstructor]
public MyClass(System.Reflection.ParameterInfo[] decoy) : base()
{
MyProperty = false;
}
Stumbled across this just now, you can also set the tag in the XML documentation, in one of my models, I have this defined
/// <summary>
/// Note content
/// </summary>
/// <example>Any text for a note.</example>
public string Note { get; set; }
which ends up looking like this in the swagger documentation when selecting "Try It Now"
Hope that helps someone!
Using .NET 5 with Swashbuckle.AspNetCore 5.6.3, the only way I could get this to work efficiently is this:
public class ExampleDocFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
string ToCamelCase(string name) => char.ToLowerInvariant(name[0]) + name.Substring(1);
if (schema.Properties == null) return;
var setProperties = context.Type.GetProperties().ToList().Where(f => f.GetCustomAttribute<DefaultValueAttribute>() != null).Where(f => schema.Properties.Any(n => n.Key.Equals(ToCamelCase(f.Name)))).ToDictionary(f => f, f => f.GetCustomAttribute<DefaultValueAttribute>());
foreach (var prop in setProperties) schema.Properties[ToCamelCase(prop.Key.Name)].Example = OpenApiAnyFactory.CreateFor(schema.Properties[ToCamelCase(prop.Key.Name)], prop.Value.Value);
}
}
To use this - in your startup.cs:
services.AddSwaggerGen(swagger => {
...
swagger.SchemaFilter<ExampleDocFilter>();
});

Access to a property with Interface cast

ActionBase, ActionA, ActionB and ActionC are Entities (from a database). ActionA, ActionB and ActionC are derived type of ActionBase.
ActionB and ActionC implements ISpecialAction with a SpecialProperty.
ex :
public interface ISpecialAction
{
Guid SpecialProperty { get; }
}
public partial class ActionBase
{
public objectX OnePropertyBase { get; set; }
}
public partial class ActionA : ActionBase
{
public objectY OnePropertyA { get; set; }
}
public partial class ActionB:ActionBase,ISpecialAction
{
public objectZ OnePropertyB { get; set; }
public Guid SpecialProperty
{
get
{
return OnePropertyB.ID;
}
}
}
public partial class ActionC : ActionBase ,ISpecialAction
{
public objectW OnePropertyC { get; set; }
public Guid SpecialProperty
{
get
{
return OnePropertyC.ID;
}
}
}
My problem is that SpecialProperty is build from other Properties of the objects (ActionB or ActionC) and when the cast (to ISpecialAction) is done, OtherProperty and OtherProperty2 are null.
I tried :
GetActionBase().ToList().Where(x=>x is ISpecialAction && ((dynamic) x).SpecialProperty== p_SpecialProperty);
GetActionBase().ToList().Where(x=>x is ISpecialAction && ((ISpecialAction) x).SpecialProperty== p_SpecialProperty);
GetActionBase().ToList().OfType<ISpecialAction>().Where(x => x.SpecialProperty== p_SpecialProperty).Cast<ActionBase>();
return GetActionOnGoing().ToList().OfType<ICityAction>().Cast<ActionBase>().Where(x => ((dynamic)x).CityId == p_CityId);
remark : OfType<> doesn't works with an Interface in Linq to entities but is ok in Linq to object
How do I access my property interface without knowing the type of the object?
I might missed something but this is Ok with the code you provided :
public class objectX
{
}
public class objectY
{
}
public class objectZ
{
public Guid ID { get { return Guid.NewGuid();} }
}
public class objectW
{
public Guid ID { get { return new Guid(); } }
}
class Program
{
private static Guid p_SpecialProperty;
static void Main(string[] args)
{
var result = GetActionBase().ToList().Where(x => x is ISpecialAction && ((dynamic)x).SpecialProperty == p_SpecialProperty).FirstOrDefault();
var result1 = GetActionBase().ToList().Where(x => x is ISpecialAction && ((ISpecialAction)x).SpecialProperty == p_SpecialProperty).FirstOrDefault();
var result2 = GetActionBase().ToList().OfType<ISpecialAction>().Where(x => x.SpecialProperty == p_SpecialProperty).Cast<ActionBase>().FirstOrDefault();
}
private static IEnumerable<ActionBase> GetActionBase()
{
return new List<ActionBase> {new ActionA{OnePropertyA= new objectY()}, new ActionB{OnePropertyB=new objectZ()},new ActionC{OnePropertyC=new objectW()} };
}
}
Not sure if I exactly understand your question, but could you try using an intermediate interface, such as:
public interface ISpecialActionB : ISpecialAction
{
objectZ OnePropertyB { get; set; }
}
public class ActionB : ActionBase, ISpecialActionB
{
//same stuff
}
and casting to that instead.
var b = new ActionB{OnePropertyB = new Whatever()};
var bAsSpecial = b as ISpecialActionB;
var whatever = b.OnePropertyB; // should not be null
It' ok.
Your example run very well without problem so I searched in a other way : AutoMapper.
l_List.Actions = Mapper.Map<List<ActionBase>, Action[]>(l_ActionManagement.GetActionBySpecialId(l_Special.ID).ToList());
The problem was not interfaces or Linq queries but it was that automapper need an empty constructor and in this constructor, I need to initialize OnePropertyB and OnePropertyC to compute SpecialProperty.
Thanks

MVC3 Validation with Lightspeed

My ORM (LightSpeed) generates this for Animals table, with Name and Age. Using MVC3 and Razor
[Serializable]
[System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[System.ComponentModel.DataObject]
[Table(IdColumnName="AnimalID", IdentityMethod=IdentityMethod.IdentityColumn)]
public partial class Animal : Entity<int>
{
[ValidatePresence]
[ValidateLength(0, 50)]
private string _name;
[ValidateComparison(ComparisonOperator.GreaterThan, 0)]
private int _age;
public const string NameField = "Name";
public const string AgeField = "Age";
[System.Diagnostics.DebuggerNonUserCode]
[Required] // ****I put this in manually to get Name required working
public string Name
{
get { return Get(ref _name, "Name"); }
set { Set(ref _name, value, "Name"); }
}
[System.Diagnostics.DebuggerNonUserCode]
public int Age
{
get { return Get(ref _age, "Age"); }
set { Set(ref _age, value, "Age"); }
}
With [Required] attribute added:
With no [Required] attribute added: (notice LightSpeed strange rendering of validation)
With name filled in:
In images above - the validation at the top is LightSpeed (put into ValidationSummary) and at the side is MVC3 (put into ValidationMessageFor)
Am only using Server Side validation currently.
Question: How do I get LightSpeed validation working well in MVC3?
I think it is something in this area http://www.mindscapehq.com/staff/jeremy/index.php/2009/03/aspnet-mvc-part4/
For the server side validation - you will want to use a custom model binder which emits the errors from LightSpeed validation more precisely rather than the leveraging the DefaultModelBinder behavior. Have a look at either directly using or adapting the EntityModelBinder from the community code library for Mvc
http://www.mindscapehq.com/forums/Thread.aspx?PostID=12051
See link http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=4093
Jeremys answer (Mindscape have great support!)
public class EntityModelBinder2 : DefaultModelBinder
{
public static void Register(Assembly assembly)
{
ModelBinders.Binders.Add(typeof(Entity), new EntityModelBinder2());
foreach (Type type in assembly.GetTypes())
{
if (typeof(Entity).IsAssignableFrom(type))
{
ModelBinders.Binders.Add(type, new EntityModelBinder2());
}
}
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = base.BindModel(controllerContext, bindingContext);
if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType))
{
Entity entity = (Entity)result;
if (!entity.IsValid)
{
foreach (var state in bindingContext.ModelState.Where(s => s.Value.Errors.Count > 0))
{
state.Value.Errors.Clear();
}
foreach (var error in entity.Errors)
{
if (error.ErrorMessage.EndsWith("is invalid")) continue;
bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom", error.ErrorMessage);
}
}
}
return result;
}
}
and in Global.asax register using:
EntityModelBinder2.Register(typeof(MyEntity).Assembly);
The Register call sets up the model binder to be used for each entity type in your model assembly so modify as required.
You can get client side validation working with Lightspeed nightly builds from 04/04/2011 onwards.
Create a validator provider as follows:
public class LightspeedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private string GetDisplayName(string name)
{
return name; // go whatever processing is required, eg decamelise, replace "_" with " " etc
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
if(typeof(Entity).IsAssignableFrom(metadata.ContainerType))
{
List<Attribute> newAttributes = new List<Attribute>(attributes);
var attr = DataAnnotationBuilder.GetDataAnnotations(metadata.ContainerType, metadata.PropertyName, GetDisplayName(metadata.PropertyName));
newAttributes.AddRange(attr);
return base.GetValidators(metadata, context, newAttributes);
}
return base.GetValidators(metadata, context, attributes);
}
}
Then in Application_Start() add
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LightspeedModelValidatorProvider());

Resources