Access properties of <T> object - .net-core

This is the extension method I have written :
public static class ExtensionMethods
{
public static string Syngen<T>(this IEnumerable<T> list, string separator)
{
StringBuilder sb = new StringBuilder();
foreach (T obj in list)
{
if (sb.Length > 0)
{
sb.Append(separator);
}
sb.Append(obj);
}
return sb.ToString();
}
}
This is where I am accessing from a razor component
-- Questionnaire is an class containing some properties
context.Items.Syngen<Questionnaire>(":")
How do I access the property of this class? (Obj) I need to access one property of this object.
I have even used Questionnaire instead of T but still unable to access obj. During runtime I am able to see all the properties of the obj, but during design/compile time, I am unable to list the properties of obj.

Assuming your objects have the same interface or base class implementing the property.
public static string Syngen<T>(this IEnumerable<T> list, string separator)
where T : ISomeInterfaceOrBaseClass =>
list.Select(a => a.SomeProperty).Aggregate((a,b) => $"{a}{seperator}{b}" );
public interface ISomeInterfaceOrBaseClass
{
string SomeProperty { get; }
}

public static string Syngen<T>(this IEnumerable<T> list, string separator) where T : class
{
StringBuilder sb = new StringBuilder();
foreach (T obj in list)
{
if (sb.Length > 0)
{
sb.Append(separator);
}
sb.Append(obj);
}
return sb.ToString();
}
This worked

Related

How do I find settable properties with Json.NET using a ContractResolver? [duplicate]

Is there a way to ignore get-only properties using the Json.NET serializer but without using JsonIgnore attributes?
For example, I have a class with these get properties:
public Keys Hotkey { get; set; }
public Keys KeyCode
{
get
{
return Hotkey & Keys.KeyCode;
}
}
public Keys ModifiersKeys
{
get
{
return Hotkey & Keys.Modifiers;
}
}
public bool Control
{
get
{
return (Hotkey & Keys.Control) == Keys.Control;
}
}
public bool Shift
{
get
{
return (Hotkey & Keys.Shift) == Keys.Shift;
}
}
public bool Alt
{
get
{
return (Hotkey & Keys.Alt) == Keys.Alt;
}
}
public Modifiers ModifiersEnum
{
get
{
Modifiers modifiers = Modifiers.None;
if (Alt) modifiers |= Modifiers.Alt;
if (Control) modifiers |= Modifiers.Control;
if (Shift) modifiers |= Modifiers.Shift;
return modifiers;
}
}
public bool IsOnlyModifiers
{
get
{
return KeyCode == Keys.ControlKey || KeyCode == Keys.ShiftKey || KeyCode == Keys.Menu;
}
}
public bool IsValidKey
{
get
{
return KeyCode != Keys.None && !IsOnlyModifiers;
}
}
Do I need to add [JsonIgnore] to all of them (I also have many other classes), or there is better way to ignore all get-only properties?
You can do this by implementing a custom IContractResolver and using that during serialization. If you subclass the DefaultContractResolver, this becomes very easy to do:
class WritablePropertiesOnlyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
return props.Where(p => p.Writable).ToList();
}
}
Here is a test program demonstrating how to use it:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
class Program
{
static void Main(string[] args)
{
Widget w = new Widget { Id = 2, Name = "Joe Schmoe" };
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new WritablePropertiesOnlyResolver()
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
}
class Widget
{
public int Id { get; set; }
public string Name { get; set; }
public string LowerCaseName
{
get { return (Name != null ? Name.ToLower() : null); }
}
}
Here is the output of the above. Notice that the read-only property LowerCaseName is not included in the output.
{"Id":2,"Name":"Joe Schmoe"}
Use the OptIn mode of JSON.net and you'll only need to decorate the properties you want to serialize. This isn't as good as automatically opting out all read only properties, but it can save you some work.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty]
public string serializedProp { get; set; }
public string nonSerializedProp { get; set; }
}
Udate: Added another possibility using reflection
If the above solution still isn't quite what you're looking for, you could use reflection to make dictionary objects which would then be serialized. Of course the example below will only work for simple classes, so you would need to add recursion if your classes contain other classes. This should at least point you in the right direction.
The subroutine to put the filtered result into a dictionary:
private Dictionary<String, object> ConvertToDictionary(object classToSerialize)
{
Dictionary<String, object> resultDictionary = new Dictionary<string, object>();
foreach (var propertyInfo in classToSerialize.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (propertyInfo.CanWrite) resultDictionary.Add(propertyInfo.Name, propertyInfo.GetValue(classToSerialize, null));
}
return resultDictionary;
}
A snippet showing its use:
SampleClass sampleClass = new SampleClass();
sampleClass.Hotkey = Keys.A;
var toSerialize = ConvertToDictionary(sampleClass);
String resultText = JsonConvert.SerializeObject(toSerialize);
You can use a contract resolver like this:
public class ExcludeCalculatedResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = _ => ShouldSerialize(member);
return property;
}
internal static bool ShouldSerialize(MemberInfo memberInfo)
{
var propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo == null)
{
return false;
}
if (propertyInfo.SetMethod != null)
{
return true;
}
var getMethod = propertyInfo.GetMethod;
return Attribute.GetCustomAttribute(getMethod, typeof(CompilerGeneratedAttribute)) != null;
}
}
It will exclude calculated properties but include C#6 get only properties and all properties with a set method.
Json.net does have the ability to conditionally serialize properties without an attribute or contract resolver. This is especially useful if you don't want your project to have a dependency on Json.net.
As per the Json.net documentation
To conditionally serialize a property, add a method that returns boolean with
the same name as the property and then prefix the method name with
ShouldSerialize. The result of the method determines whether the
property is serialized. If the method returns true then the property
will be serialized, if it returns false then the property will be
skipped.

JObject.ToBsonDocument dropping values

I'm inserting raw JSON into a collection and finding that what is stored in the database is missing the values. For example, my collection is a collection of BsonDocuments:
_products = database.GetCollection<BsonDocument>("products");
The code to insert the JSON into the collection:
public int AddProductDetails(JObject json)
{
var doc = json.ToBsonDocument(DictionarySerializationOptions.Document);
_products.Insert(doc);
}
The JSON that is passed in looks like this:
{
"Id": 1,
"Tags": [
"book",
"database"
],
"Name": "Book Name",
"Price": 12.12
}
But, what is persisted in the collection is just the properties with no values.
{
"_id": {
"$oid": "5165c7e10fdb8c09f446d720"
},
"Id": [],
"Tags": [
[],
[]
],
"Name": [],
"Price": []
}
Why are the values being dropped?
This does what I was expecting.
public int AddProductDetails(JObject json)
{
BsonDocument doc = BsonDocument.Parse(json.ToString());
_products.Insert(doc);
}
I ran into this issue when I had a C# class with a property of type JObject.
My Solution was to create JObjectSerializer for MondoDB and add the attribute to the property so Mongo serializer uses it. I assume if I tried hard enough I could register the below serializer in Mongo as the global one for this type as well.
Register serializer for property processing:
[BsonSerializer(typeof(JObjectSerializer))]
public JObject AdditionalData { get; set; }
The serializer itself:
public class JObjectSerializer : SerializerBase<JObject> // IBsonSerializer<JObject>
{
public override JObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var myBSONDoc = BsonDocumentSerializer.Instance.Deserialize(context);
return JObject.Parse(myBSONDoc.ToString());
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JObject value)
{
var myBSONDoc = MongoDB.Bson.BsonDocument.Parse(value.ToString());
BsonDocumentSerializer.Instance.Serialize(context, myBSONDoc);
}
}
The problem when using JObject.ToString, BsonDocument.Parse, etc. is the performance is not very good because you do the same operations multiple times, you do string allocations, parsing, etc.
So, I have written a function that converts a JObject to an IEnumerable<KeyValuePair<string, object>> (only using enumerations), which is a type usable by one of the BsonDocument constructors. Here is the code:
public static BsonDocument ToBsonDocument(this JObject jo)
{
if (jo == null)
return null;
return new BsonDocument(ToEnumerableWithObjects(jo));
}
public static IEnumerable<KeyValuePair<string, object>> ToEnumerableWithObjects(this JObject jo)
{
if (jo == null)
return Enumerable.Empty<KeyValuePair<string, object>>();
return new JObjectWrapper(jo);
}
private class JObjectWrapper : IEnumerable<KeyValuePair<string, object>>
{
private JObject _jo;
public JObjectWrapper(JObject jo)
{
_jo = jo;
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => new JObjectWrapperEnumerator(_jo);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public static object ToValue(JToken token)
{
object value;
switch (token.Type)
{
case JTokenType.Object:
value = new JObjectWrapper((JObject)token);
break;
case JTokenType.Array:
value = new JArrayWrapper((JArray)token);
break;
default:
if (token is JValue jv)
{
value = ((JValue)token).Value;
}
else
{
value = token.ToString();
}
break;
}
return value;
}
}
private class JArrayWrapper : IEnumerable
{
private JArray _ja;
public JArrayWrapper(JArray ja)
{
_ja = ja;
}
public IEnumerator GetEnumerator() => new JArrayWrapperEnumerator(_ja);
}
private class JArrayWrapperEnumerator : IEnumerator
{
private IEnumerator<JToken> _enum;
public JArrayWrapperEnumerator(JArray ja)
{
_enum = ja.GetEnumerator();
}
public object Current => JObjectWrapper.ToValue(_enum.Current);
public bool MoveNext() => _enum.MoveNext();
public void Reset() => _enum.Reset();
}
private class JObjectWrapperEnumerator : IEnumerator<KeyValuePair<string, object>>
{
private IEnumerator<KeyValuePair<string, JToken>> _enum;
public JObjectWrapperEnumerator(JObject jo)
{
_enum = jo.GetEnumerator();
}
public KeyValuePair<string, object> Current => new KeyValuePair<string, object>(_enum.Current.Key, JObjectWrapper.ToValue(_enum.Current.Value));
public bool MoveNext() => _enum.MoveNext();
public void Dispose() => _enum.Dispose();
public void Reset() => _enum.Reset();
object IEnumerator.Current => Current;
}
Have you tried using the BsonSerializer?
using MongoDB.Bson.Serialization;
[...]
var document = BsonSerializer.Deserialize<BsonDocument>(json);
BsonSerializer works with strings, so if the JSON argument is a JObject(or JArray, JRaw etc) you have to serialize it with JsonConvert.SerializeObject()
Here is an updated version of Andrew DeVries's answer that includes handling for serializing/deserializing null values.
public class JObjectSerializer : SerializerBase<JObject>
{
public override JObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
if (context.Reader.CurrentBsonType != BsonType.Null)
{
var myBSONDoc = BsonDocumentSerializer.Instance.Deserialize(context);
return JObject.Parse(myBSONDoc.ToStrictJson());
}
else
{
context.Reader.ReadNull();
return null;
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JObject value)
{
if (value != null)
{
var myBSONDoc = BsonDocument.Parse(value.ToString());
BsonDocumentSerializer.Instance.Serialize(context, myBSONDoc);
}
else
{
context.Writer.WriteNull();
}
}
}
The ToStrictJson() call is an extension method that wraps the call to the built-in BSON ToJson() method to include setting the output mode to strict. If this is not done, the parsing will fail because BSON type constructors will remain in the JSON output (ObjectId(), for example).
Here is the implementation of ToStrictJson() as well:
public static class MongoExtensionMethods
{
/// <summary>
/// Create a JsonWriterSettings object to use when serializing BSON docs to JSON.
/// This will force the serializer to create valid ("strict") JSON.
/// Without this, Object IDs and Dates are ouput as {"_id": ObjectId(ds8f7s9d87f89sd9f8d9f7sd9f9s8d)}
/// and {"date": ISODate("2020-04-14 14:30:00:000")} respectively, which is not valid JSON
/// </summary>
private static JsonWriterSettings jsonWriterSettings = new JsonWriterSettings()
{
OutputMode = JsonOutputMode.Strict
};
/// <summary>
/// Custom extension method to convert MongoDB objects to JSON using the OutputMode = Strict setting.
/// This ensure that the resulting string is valid JSON.
/// </summary>
/// <typeparam name="TNominalType">The type of object to convert to JSON</typeparam>
/// <param name="obj">The object to conver to JSON</param>
/// <returns>A strict JSON string representation of obj.</returns>
public static string ToStrictJson<TNominalType>(this TNominalType obj)
{
return BsonExtensionMethods.ToJson<TNominalType>(obj, jsonWriterSettings);
}
}
I use the following. It's based on Simon's answer, thanks for the idea, and works in the same way, avoiding unnecessary serialization / deserialization into string.
It's just a bit more compact, thanks to Linq and C# 10:
public static BsonDocument ToBsonDocument(this JObject o) =>
new(o.Properties().Select(p => new BsonElement(p.Name, p.Value.ToBsonValue())));
public static BsonValue ToBsonValue(this JToken t) =>
t switch
{
JObject o => o.ToBsonDocument(),
JArray a => new BsonArray(a.Select(ToBsonValue)),
JValue v => BsonValue.Create(v.Value),
_ => throw new NotSupportedException($"ToBsonValue: {t}")
};
Most of the answers here involve serializing to and then deserializing from a string. Here is a solution that serializes to/from raw BSON instead. It requires the Newtonsoft.Json.Bson nuget package.
using System.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
namespace Zonal.EventPublisher.Worker
{
public class JObjectSerializer : SerializerBase<JObject>
{
public override JObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
using (var stream = new MongoDB.Bson.IO.ByteBufferStream(context.Reader.ReadRawBsonDocument()))
using (JsonReader reader = new BsonDataReader(stream))
{
return JObject.Load(reader);
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JObject value)
{
using (var stream = new MemoryStream())
using (JsonWriter writer = new BsonDataWriter(stream))
{
value.WriteTo(writer);
var buffer = new MongoDB.Bson.IO.ByteArrayBuffer(stream.ToArray());
context.Writer.WriteRawBsonDocument(buffer);
}
}
}
}
Don't forget to register the serializer with:
BsonSerializer.RegisterSerializer(new JObjectSerializer());
After that you can convert your JObject to a BsonDocument by using the MongoDB.Bson.BsonExtensionMethods.ToBsonDocument extension method:
var myBsonDocument = myJObject.ToBsonDocument()
And convert a BsonDocument back to a JObject by using the MongoDB.Bson.Serialization.BsonSerializer class:
var myJObject = BsonSerializer.Deserialize<JObject>(myBsonDocument);

JSON.Net - Change $type field to another name?

When using Json.Net, I understand how to get the $type property into the rendered json, but is there a way to change that field name? I need to use "__type" instead of "$type".
http://json.codeplex.com/workitem/22429
"I would rather keep $type hard coded and consistent."
Consistent with what I wonder?
http://json.codeplex.com/workitem/21989
I would rather not - I think this is too specific to me and I don't
want to go overboard with settings. At some point I will probably
implement this - http://json.codeplex.com/workitem/21856 - allowing
people to read/write there own meta properties in the JSON and you
could reimplement type name handling with a new property name. The
other option is just to modify the source code for yourself to have
that property name.
And more recently, Issue #36: Customizable $type property name feature:
I'd rather not
This is my solution...
json.Replace("\"$type\": \"", "\"type\": \"");
Looks like this is hardcoded as public const string TypePropertyName = "$type"; in Newtonsoft.Json.Serialization.JsonTypeReflector which is internal static class unfortunately.
I needed this myself, and the only thing I can think of is having custom modified version of json.net itself. Which is of course is a major pita.
when serializing, there is a nice way to override the property name:
public class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name == "$type") name = "__type";
base.WritePropertyName(name, escape);
}
}
var serializer = new JsonSerializer();
var writer = new StreamWriter(stream) { AutoFlush = true };
serializer.Serialize(new CustomJsonWriter(writer), objectToSerialize);
I haven't tried deserialization yet, but in worst case I could use:
json.Replace("\"__type": \"", "\"type\": \"$type\");
I had to do this for my UI REST API as Angular.js disregards fields names starting with a dollar sign ($).
So here's a solution that renames $type to __type for the whole Web API and works both for serialization and deserialization.
In order to be able to use a custom JsonWriter and a custom JsonReader (as proposed in the other answers to this question), we have to inherit the JsonMediaTypeFormatter and override the corresponding methods:
internal class CustomJsonNetFormatter : JsonMediaTypeFormatter
{
public override JsonReader CreateJsonReader(Type type, Stream readStream, Encoding effectiveEncoding)
{
return new CustomJsonReader(readStream, effectiveEncoding);
}
public override JsonWriter CreateJsonWriter(Type type, Stream writeStream, Encoding effectiveEncoding)
{
return new CustomJsonWriter(writeStream, effectiveEncoding);
}
private class CustomJsonWriter : JsonTextWriter
{
public CustomJsonWriter(Stream writeStream, Encoding effectiveEncoding)
: base(new StreamWriter(writeStream, effectiveEncoding))
{
}
public override void WritePropertyName(string name, bool escape)
{
if (name == "$type") name = "__type";
base.WritePropertyName(name, escape);
}
}
private class CustomJsonReader : JsonTextReader
{
public CustomJsonReader(Stream readStream, Encoding effectiveEncoding)
: base(new StreamReader(readStream, effectiveEncoding))
{
}
public override bool Read()
{
var hasToken = base.Read();
if (hasToken && TokenType == JsonToken.PropertyName && Value != null && Value.Equals("__type"))
{
SetToken(JsonToken.PropertyName, "$type");
}
return hasToken;
}
}
}
Of course you need to register the custom formatter in your WebApiConfig. So we replace the default Json.NET formatter with our custom one:
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Add(new CustomJsonNetFormatter());
Done.
We had a need for this so I created a custom JsonReader. We're are using rest in our MS web services with complex data models and needed to replace the "__type" property with "$type."
class MSJsonReader : JsonTextReader
{
public MSJsonTextReader(TextReader reader) : base(reader) { }
public override bool Read()
{
var hasToken = base.Read();
if (hasToken && base.TokenType == JsonToken.PropertyName && base.Value != null && base.Value.Equals("__type"))
base.SetToken(JsonToken.PropertyName, "$type");
return hasToken;
}
}
Here is how we use it.
using(JsonReader jr = new MSJsonTextReader(sr))
{
JsonSerializer s = new JsonSerializer();
s.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
s.NullValueHandling = NullValueHandling.Ignore;
s.TypeNameHandling = TypeNameHandling.Auto; // Important!
s.Binder = new MSRestToJsonDotNetSerializationBinder("Server.DataModelsNamespace", "Client.GeneratedModelsNamespace");
T deserialized = s.Deserialize<T>(jr);
return deserialized;
}
Here is our MSRestToJsonDotNetSerializationBinder that completes the compatibility between MS rest and Json.Net.
class MSRestToJsonDotNetSerializationBinder : System.Runtime.Serialization.SerializationBinder
{
public string ServiceNamespace { get; set; }
public string LocalNamespace { get; set; }
public MSRestToJsonDotNetSerializationBinder(string serviceNamespace, string localNamespace)
{
if (serviceNamespace.EndsWith("."))
serviceNamespace = serviceNamespace.Substring(0, -1);
if(localNamespace.EndsWith("."))
localNamespace = localNamespace.Substring(0, -1);
ServiceNamespace = serviceNamespace;
LocalNamespace = localNamespace;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = string.Format("{0}:#{1}", serializedType.Name, ServiceNamespace); // MS format
}
public override Type BindToType(string assemblyName, string typeName)
{
string jsonDotNetType = string.Format("{0}.{1}", LocalNamespace, typeName.Substring(0, typeName.IndexOf(":#")));
return Type.GetType(jsonDotNetType);
}
}
You could also do it this way:
[JsonConverter(typeof(JsonSubtypes), "ClassName")]
public class Annimal
{
public virtual string ClassName { get; }
public string Color { get; set; }
}
You will need the JsonSubtypes
converter that is not part of Newtonsoft.Json project.
There is another option that allows to serialize custom type property name in Json.NET. The idea is do not write default $type property, but introduce type name as property of class itself.
Suppose we have a Location class:
public class Location
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
First, we need to introduce type property name and modify the class as demonstrated below:
public class Location
{
[JsonProperty("__type")]
public string EntityTypeName
{
get
{
var typeName = string.Format("{0}, {1}", GetType().FullName, GetType().Namespace);
return typeName;
}
}
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Then, set JsonSerializerSettings.TypeNameHandling to TypeNameHandling.None in order the deserializer to skip the rendering of default $type attribute.
That's it.
Example
var point = new Location() { Latitude = 51.5033630, Longitude = -0.1276250 };
var jsonLocation = JsonConvert.SerializeObject(point, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.None, //do not write type property(!)
});
Console.WriteLine(jsonLocation);
Result
{"__type":"Namespace.Location, Namespace","Latitude":51.503363,"Longitude":-0.127625}
Using a custom converter should get the job done.
public CustomConverter : JsonConverter
{
public override bool CanWrite => true;
public override bool CanRead => true;
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
=> throw new NotImplementedException();
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
var jOjbect = (JObject)JToken.FromObject(value);
jOjbect.Add(new JProperty("type", value.GetType().Name));
jOjbect.WriteTo(writer);
}
}

Best way to trim strings after data entry. Should I create a custom model binder?

I'm using ASP.NET MVC and I'd like all user entered string fields to be trimmed before they're inserted into the database. And since I have many data entry forms, I'm looking for an elegant way to trim all strings instead of explicitly trimming every user supplied string value. I'm interested to know how and when people are trimming strings.
I thought about perhaps creating a custom model binder and trimming any string values there...that way, all my trimming logic is contained in one place. Is this a good approach? Are there any code samples that do this?
public class TrimModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.PropertyType == typeof(string))
{
var stringValue = (string)value;
if (!string.IsNullOrWhiteSpace(stringValue))
{
value = stringValue.Trim();
}
else
{
value = null;
}
}
base.SetProperty(controllerContext, bindingContext,
propertyDescriptor, value);
}
}
How about this code?
ModelBinders.Binders.DefaultBinder = new TrimModelBinder();
Set global.asax Application_Start event.
This is #takepara same resolution but as an IModelBinder instead of DefaultModelBinder so that adding the modelbinder in global.asax is through
ModelBinders.Binders.Add(typeof(string),new TrimModelBinder());
The class:
public class TrimModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult== null || valueResult.AttemptedValue==null)
return null;
else if (valueResult.AttemptedValue == string.Empty)
return string.Empty;
return valueResult.AttemptedValue.Trim();
}
}
based on #haacked post:
http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx
One improvement to #takepara answer.
Somewere in project:
public class NoTrimAttribute : Attribute { }
In TrimModelBinder class change
if (propertyDescriptor.PropertyType == typeof(string))
to
if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))
and you can mark properties to be excluded from trimming with [NoTrim] attribute.
In ASP.Net Core 2 this worked for me. I'm using the [FromBody] attribute in my controllers and JSON input. To override the string handling in the JSON deserialization I registered my own JsonConverter:
services.AddMvcCore()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
})
And this is the converter:
public class TrimmingStringConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.Value is string value)
{
return value.Trim();
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
With improvements in C# 6, you can now write a very compact model binder that will trim all string inputs:
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
You need to include this line somewhere in Application_Start() in your Global.asax.cs file to use the model binder when binding strings:
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
I find it is better to use a model binder like this, rather than overriding the default model binder, because then it will be used whenever you are binding a string, whether that's directly as a method argument or as a property on a model class. However, if you override the default model binder as other answers here suggest, that will only work when binding properties on models, not when you have a string as an argument to an action method
Edit: a commenter asked about dealing with the situation when a field should not be validated. My original answer was reduced to deal just with the question the OP had posed, but for those who are interested, you can deal with validation by using the following extended model binder:
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled;
var unvalidatedValueProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var value = unvalidatedValueProvider == null ?
bindingContext.ValueProvider.GetValue(bindingContext.ModelName) :
unvalidatedValueProvider.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
Another variant of #takepara's answer but with a different twist:
1) I prefer the opt-in "StringTrim" attribute mechanism (rather than the opt-out "NoTrim" example of #Anton).
2) An additional call to SetModelValue is required to ensure the ModelState is populated correctly and the default validation/accept/reject pattern can be used as normal, i.e. TryUpdateModel(model) to apply and ModelState.Clear() to accept all changes.
Put this in your entity/shared library:
/// <summary>
/// Denotes a data field that should be trimmed during binding, removing any spaces.
/// </summary>
/// <remarks>
/// <para>
/// Support for trimming is implmented in the model binder, as currently
/// Data Annotations provides no mechanism to coerce the value.
/// </para>
/// <para>
/// This attribute does not imply that empty strings should be converted to null.
/// When that is required you must additionally use the <see cref="System.ComponentModel.DataAnnotations.DisplayFormatAttribute.ConvertEmptyStringToNull"/>
/// option to control what happens to empty strings.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class StringTrimAttribute : Attribute
{
}
Then this in your MVC application/library:
/// <summary>
/// MVC model binder which trims string values decorated with the <see cref="StringTrimAttribute"/>.
/// </summary>
public class StringTrimModelBinder : IModelBinder
{
/// <summary>
/// Binds the model, applying trimming when required.
/// </summary>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get binding value (return null when not present)
var propertyName = bindingContext.ModelName;
var originalValueResult = bindingContext.ValueProvider.GetValue(propertyName);
if (originalValueResult == null)
return null;
var boundValue = originalValueResult.AttemptedValue;
// Trim when required
if (!String.IsNullOrEmpty(boundValue))
{
// Check for trim attribute
if (bindingContext.ModelMetadata.ContainerType != null)
{
var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
.FirstOrDefault(propertyInfo => propertyInfo.Name == bindingContext.ModelMetadata.PropertyName);
if (property != null && property.GetCustomAttributes(true)
.OfType<StringTrimAttribute>().Any())
{
// Trim when attribute set
boundValue = boundValue.Trim();
}
}
}
// Register updated "attempted" value with the model state
bindingContext.ModelState.SetModelValue(propertyName, new ValueProviderResult(
originalValueResult.RawValue, boundValue, originalValueResult.Culture));
// Return bound value
return boundValue;
}
}
If you don't set the property value in the binder, even when you don't want to change anything, you will block that property from ModelState altogether! This is because you are registered as binding all string types, so it appears (in my testing) that the default binder will not do it for you then.
Extra info for anyone searching how to do this in ASP.NET Core 1.0. Logic has changed quite a lot.
I wrote a blog post about how to do it, it explains things in bit more detailed
So ASP.NET Core 1.0 solution:
Model binder to do the actual trimming
public class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary propertyBinders) : base(propertyBinders)
{
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if(result.Model is string)
{
string resultStr = (result.Model as string).Trim();
result = ModelBindingResult.Success(resultStr);
}
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
Also you need Model Binder Provider in the latest version, this tells that should this binder be used for this model
public class TrimmingModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary();
foreach (var property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
Then it has to be registered in Startup.cs
services.AddMvc().AddMvcOptions(options => {
options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
});
In case of MVC Core
Binder:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Threading.Tasks;
public class TrimmingModelBinder
: IModelBinder
{
private readonly IModelBinder FallbackBinder;
public TrimmingModelBinder(IModelBinder fallbackBinder)
{
FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null &&
valueProviderResult.FirstValue is string str &&
!string.IsNullOrEmpty(str))
{
bindingContext.Result = ModelBindingResult.Success(str.Trim());
return Task.CompletedTask;
}
return FallbackBinder.BindModelAsync(bindingContext);
}
}
Provider:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
public class TrimmingModelBinderProvider
: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(string))
{
return new TrimmingModelBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
return null;
}
}
Registration function:
public static void AddStringTrimmingProvider(this MvcOptions option)
{
var binderToFind = option.ModelBinderProviders
.FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));
if (binderToFind == null)
{
return;
}
var index = option.ModelBinderProviders.IndexOf(binderToFind);
option.ModelBinderProviders.Insert(index, new TrimmingModelBinderProvider());
}
Register:
service.AddMvc(option => option.AddStringTrimmingProvider())
I created value providers to trim the query string parameter values and the form values. This was tested with ASP.NET Core 3 and works perfectly.
public class TrimmedFormValueProvider
: FormValueProvider
{
public TrimmedFormValueProvider(IFormCollection values)
: base(BindingSource.Form, values, CultureInfo.InvariantCulture)
{ }
public override ValueProviderResult GetValue(string key)
{
ValueProviderResult baseResult = base.GetValue(key);
string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
return new ValueProviderResult(new StringValues(trimmedValues));
}
}
public class TrimmedQueryStringValueProvider
: QueryStringValueProvider
{
public TrimmedQueryStringValueProvider(IQueryCollection values)
: base(BindingSource.Query, values, CultureInfo.InvariantCulture)
{ }
public override ValueProviderResult GetValue(string key)
{
ValueProviderResult baseResult = base.GetValue(key);
string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
return new ValueProviderResult(new StringValues(trimmedValues));
}
}
public class TrimmedFormValueProviderFactory
: IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context.ActionContext.HttpContext.Request.HasFormContentType)
context.ValueProviders.Add(new TrimmedFormValueProvider(context.ActionContext.HttpContext.Request.Form));
return Task.CompletedTask;
}
}
public class TrimmedQueryStringValueProviderFactory
: IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
context.ValueProviders.Add(new TrimmedQueryStringValueProvider(context.ActionContext.HttpContext.Request.Query));
return Task.CompletedTask;
}
}
Then register the value provider factories in the ConfigureServices() function in Startup.cs
services.AddControllersWithViews(options =>
{
int formValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<FormValueProviderFactory>().Single());
options.ValueProviderFactories[formValueProviderFactoryIndex] = new TrimmedFormValueProviderFactory();
int queryStringValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
options.ValueProviderFactories[queryStringValueProviderFactoryIndex] = new TrimmedQueryStringValueProviderFactory();
});
While reading through the excellent answers and comments above, and becoming increasingly confused, I suddenly thought, hey, I wonder if there's a jQuery solution. So for others who, like me, find ModelBinders a bit bewildering, I offer the following jQuery snippet that trims the input fields before the form gets submitted.
$('form').submit(function () {
$(this).find('input:text').each(function () {
$(this).val($.trim($(this).val()));
})
});
Late to the party, but the following is a summary of adjustments required for MVC 5.2.3 if you are to handle the skipValidation requirement of the build-in value providers.
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// First check if request validation is required
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest &&
bindingContext.ModelMetadata.RequestValidationEnabled;
// determine if the value provider is IUnvalidatedValueProvider, if it is, pass in the
// flag to perform request validation (e.g. [AllowHtml] is set on the property)
var unvalidatedProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var valueProviderResult = unvalidatedProvider?.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation) ??
bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return valueProviderResult?.AttemptedValue?.Trim();
}
}
Global.asax
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
...
}
Update: This answer is out of date for recent versions of ASP.NET Core. Use Bassem's answer instead.
For ASP.NET Core, replace the ComplexTypeModelBinderProvider with a provider that trims strings.
In your startup code ConfigureServices method, add this:
services.AddMvc()
.AddMvcOptions(s => {
s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
})
Define TrimmingModelBinderProvider like this:
/// <summary>
/// Used in place of <see cref="ComplexTypeModelBinderProvider"/> to trim beginning and ending whitespace from user input.
/// </summary>
class TrimmingModelBinderProvider : IModelBinderProvider
{
class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders) { }
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
var value = result.Model as string;
if (value != null)
result = ModelBindingResult.Success(value.Trim());
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++) {
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
The ugly part of this is the copy and paste of the GetBinder logic from ComplexTypeModelBinderProvider, but there doesn't seem to be any hook to let you avoid this.
I disagree with the solution.
You should override GetPropertyValue because the data for SetProperty could also be filled by the ModelState.
To catch the raw data from the input elements write this:
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override object GetPropertyValue(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, System.Web.Mvc.IModelBinder propertyBinder)
{
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
string retval = value as string;
return string.IsNullOrWhiteSpace(retval)
? value
: retval.Trim();
}
}
Filter by propertyDescriptor PropertyType if you are really only interested in string values but it should not matter because everything what comes in is basically a string.
There have been a lot of posts suggesting an attribute approach. Here is a package that already has a trim attribute and many others: Dado.ComponentModel.Mutations or NuGet
public partial class ApplicationUser
{
[Trim, ToLower]
public virtual string UserName { get; set; }
}
// Then to preform mutation
var user = new ApplicationUser() {
UserName = " M#X_speed.01! "
}
new MutationContext<ApplicationUser>(user).Mutate();
After the call to Mutate(), user.UserName will be mutated to m#x_speed.01!.
This example will trim whitespace and case the string to lowercase. It doesn't introduce validation, but the System.ComponentModel.Annotations can be used alongside Dado.ComponentModel.Mutations.
I posted this in another thread. In asp.net core 2, I went in a different direction. I used an action filter instead. In this case the developer can either set it globally or use as an attribute for the actions he/she wants to apply the string trimming. This code runs after the model binding has taken place, and it can update the values in the model object.
Here is my code, first create an action filter:
public class TrimInputStringsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
foreach (var arg in context.ActionArguments)
{
if (arg.Value is string)
{
string val = arg.Value as string;
if (!string.IsNullOrEmpty(val))
{
context.ActionArguments[arg.Key] = val.Trim();
}
continue;
}
Type argType = arg.Value.GetType();
if (!argType.IsClass)
{
continue;
}
TrimAllStringsInObject(arg.Value, argType);
}
}
private void TrimAllStringsInObject(object arg, Type argType)
{
var stringProperties = argType.GetProperties()
.Where(p => p.PropertyType == typeof(string));
foreach (var stringProperty in stringProperties)
{
string currentValue = stringProperty.GetValue(arg, null) as string;
if (!string.IsNullOrEmpty(currentValue))
{
stringProperty.SetValue(arg, currentValue.Trim(), null);
}
}
}
}
To use it, either register as global filter or decorate your actions with the TrimInputStrings attribute.
[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
// Some business logic...
return Ok();
}
OK, I have this thing and it kinda works:
class TrimmingModelBinder : IModelBinder
{
public Task BindModelAsync (ModelBindingContext ctx)
{
if
(
ctx .ModelName is string name
&& ctx .ValueProvider .GetValue (name) .FirstValue is string v)
ctx .ModelState .SetModelValue
(
name,
new ValueProviderResult
((ctx .Result = ModelBindingResult .Success (v .Trim ())) .Model as string));
return Task .CompletedTask; }}
class AutoTrimAttribute : ModelBinderAttribute
{
public AutoTrimAttribute ()
{ this .BinderType = typeof (TrimmingModelBinder); }}
It is a shame that there is no standard feature for this though.
I adapted #Kai G's answer for System.Text.Json:
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class TrimmedStringConverter : JsonConverter<string>
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(string);
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetString() is string value ? value.Trim() : null;
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}

Passing int array as parameter in web user control

I have an int array as a property of a Web User Control. I'd like to set that property inline if possible using the following syntax:
<uc1:mycontrol runat="server" myintarray="1,2,3" />
This will fail at runtime because it will be expecting an actual int array, but a string is being passed instead. I can make myintarray a string and parse it in the setter, but I was wondering if there was a more elegant solution.
Implement a type converter, here is one, warning : quick&dirty, not for production use, etc :
public class IntArrayConverter : System.ComponentModel.TypeConverter
{
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string val = value as string;
string[] vals = val.Split(',');
System.Collections.Generic.List<int> ints = new System.Collections.Generic.List<int>();
foreach (string s in vals)
ints.Add(Convert.ToInt32(s));
return ints.ToArray();
}
}
and tag the property of your control :
private int[] ints;
[TypeConverter(typeof(IntsConverter))]
public int[] Ints
{
get { return this.ints; }
set { this.ints = value; }
}
#mathieu, thanks so much for your code. I modified it somewhat in order to compile:
public class IntArrayConverter : System.ComponentModel.TypeConverter
{
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string val = value as string;
string[] vals = val.Split(',');
System.Collections.Generic.List<int> ints = new System.Collections.Generic.List<int>();
foreach (string s in vals)
ints.Add(Convert.ToInt32(s));
return ints.ToArray();
}
}
Seems to me that the logical—and more extensible—approach is to take a page from the asp: list controls:
<uc1:mycontrol runat="server">
<uc1:myintparam>1</uc1:myintparam>
<uc1:myintparam>2</uc1:myintparam>
<uc1:myintparam>3</uc1:myintparam>
</uc1:mycontrol>
Great snippet #mathieu. I needed to use this for converting longs, but rather than making a LongArrayConverter, I wrote up a version that uses Generics.
public class ArrayConverter<T> : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string val = value as string;
if (string.IsNullOrEmpty(val))
return new T[0];
string[] vals = val.Split(',');
List<T> items = new List<T>();
Type type = typeof(T);
foreach (string s in vals)
{
T item = (T)Convert.ChangeType(s, type);
items.Add(item);
}
return items.ToArray();
}
}
This version should work with any type that is convertible from string.
[TypeConverter(typeof(ArrayConverter<int>))]
public int[] Ints { get; set; }
[TypeConverter(typeof(ArrayConverter<long>))]
public long[] Longs { get; set; }
[TypeConverter(typeof(ArrayConverter<DateTime))]
public DateTime[] DateTimes { get; set; }
Have you tried looking into Type Converters? This page looks worth a look: http://www.codeguru.com/columns/VB/article.php/c6529/
Also, Spring.Net seems to have a StringArrayConverter (http://www.springframework.net/doc-latest/reference/html/objects-misc.html - section 6.4) which, if you can feed it to ASP.net by decorating the property with a TypeConverter attribute, might work..
You could also do something like this:
namespace InternalArray
{
/// <summary>
/// Item for setting value specifically
/// </summary>
public class ArrayItem
{
public int Value { get; set; }
}
public class CustomUserControl : UserControl
{
private List<int> Ints {get {return this.ItemsToList();}
/// <summary>
/// set our values explicitly
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(List<ArrayItem>))]
public List<ArrayItem> Values { get; set; }
/// <summary>
/// Converts our ArrayItem into a List<int>
/// </summary>
/// <returns></returns>
private List<int> ItemsToList()
{
return (from q in this.Values
select q.Value).ToList<int>();
}
}
}
which will result in:
<xx:CustomUserControl runat="server">
<Values>
<xx:ArrayItem Value="1" />
</Values>
</xx:CustomUserControl>
To add child elements that make your list you need to have your control setup a certain way:
[ParseChildren(true, "Actions")]
[PersistChildren(false)]
[ToolboxData("<{0}:PageActionManager runat=\"server\" ></PageActionManager>")]
[NonVisualControl]
public class PageActionManager : Control
{
The Actions above is the name of the cproperty the child elements will be in. I use an ArrayList, as I have not testing anything else with it.:
private ArrayList _actions = new ArrayList();
public ArrayList Actions
{
get
{
return _actions;
}
}
when your contorl is initialized it will have the values of the child elements. Those you can make a mini class that just holds ints.
Do do what Bill was talking about with the list you just need to create a List property on your user control. Then you can implement it as Bill described.
You could add to the page events inside the aspx something like this:
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
YourUserControlID.myintarray = new Int32[] { 1, 2, 3 };
}
</script>
You can implement a type converter class that converts between int array and string data types.
Then decorate your int array property with the TypeConverterAttribute, specifying the class that you implemented. Visual Studio will then use your type converter for type conversions on your property.
If you use DataBinding on one of the parent Controls, you can use a DataBinding Expression:
<uc1:mycontrol runat="server" myintarray="<%# new [] {1, 2, 3} %>" />
With a custom expression builder, you can do something similar. The expression builder:
[ExpressionPrefix("Code")]
public class CodeExpressionBuilder : ExpressionBuilder
{
public override CodeExpression GetCodeExpression(System.Web.UI.BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
return new CodeSnippetExpression(entry.Expression.Trim());
}
}
Usage:
<uc1:mycontrol runat="server" myintarray="<%$ Code: new [] {1, 2, 3} %>" />

Resources