I'm having trouble when rendering a text box using #Html.TextBoxFor with a custom type I've created. My custom type looks like this:
public class Encrypted<T>
{
private readonly Lazy<T> _decrypted;
private readonly Lazy<string> _encrypted;
public static implicit operator Encrypted<T>(T value)
{
return new Encrypted<T>(value);
}
public static implicit operator string(Encrypted<T> value)
{
return value._encrypted.Value;
}
...
}
Then on my model, I have:
public class ExampleModel
{
public Encrypted<string> Name { get; set; }
}
If I manually populate the value in my controller action:
public ActionResult Index()
{
var model = new ExampleModel
{
Name = "Example Name";
};
return View(model);
}
Then on my view I have the standard #Html.TextBoxFor(m => m.Name). However, when that renders, the value of my text box gets set to: Services.Encrypted`1[System.String]`
Presumably this is because I'm using a custom type and the compiler doesn't know how to convert my type to a string value.
I've tried using a custom TypeConverter:
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
var encrypted = value as IEncrypted;
if (encrypted != null)
{
return encrypted.DecryptedValue();
}
}
return null;
}
Then on my Encrypted model I added:
[TypeConverter(typeof(EncryptedTypeConveter))]
However it doesn't seem to be using the custom TypeConverter. Does anyone know how I can resolve this?
You need to override ToString().
Related
Am trying to work with a Winform Property Grid, and I am not able to format displayed value (mind too strongly tied to wpf now)
So what I want is, there is a drop down in property grid that has its own UITypeEditor, this editor shows values such as
1 - On
2 - Off
3 - Unknown
so the property on that listens to propertyGrid changes is int and for some odd reasons I cant change it to string, so like in wpf can I have a converter sort of thing that converts 1 into 1- On and 1-On to 1 ?
how can I decorate my property or property grid to be this intelligent ?
My property looks like
[LocalizedCategory("Limits", typeof(Api.Properties.Resources))]
[LocalizedDisplayName("Maximum", typeof(Api.Properties.Resources))]
[LocalizedDescription("Maximum", typeof(Api.Properties.Resources))]
[Editor(typeof(TextConversionTypeEditor), typeof(UITypeEditor))]
public int CriticalMaximum
{
get; set;
}
Can I make my property grid display more information than just an int ?
If you can use an Enum as type of your property, then it shows available values in drop-down, otherwise you can create a TypeConverter to provide values for drop-down. To do so you can use either of these options:
Use TypeConverter of an Enum for your int Property
If values are limited and known at design-time, In this case although the property is int, you can use the converter of an Enum for your property, without overriding anything:
public class MyObject
{
[TypeConverter(typeof(MyTypeConverter))]
public int MyProperty { get; set; }
}
public class MyTypeConverter : EnumConverter
{
public MyTypeConverter() : base(typeof(MyValues)) { }
}
public enum MyValues
{
On = 1,
Off,
Unknown
}
Create your own TypeConverter which supports standard values
If you can not have an enum and your standard values are generated at run-time, you can create such TypeConverter:
public class MyTypeConverter : TypeConverter
{
Dictionary<int, string> values;
public MyTypeConverter()
{
values = new Dictionary<int, string> { { 1, "1 - On" }, { 2, "2 - Off" }, { 3, "3 - Unknown" } };
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string)) return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value != null && values.ContainsValue(value.ToString()))
return values.Where(x => x.Value == value.ToString()).FirstOrDefault().Key;
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value != null && value.GetType() == typeof(int))
return values[(int)value];
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(values.Keys);
}
}
I have created strongly typed ID classes in my project as there is currently confusion with interchangeable string ID's which is causing bugs which are easy to miss.
I have changed all the string id parameters in my action methods to the new strongly typed to realise that the MVC model binder can not now bind the strings to the new type (despite implicit string conversion operators existing for this type).
e.g.
public ActionResult Index(JobId jobId)
{
//...
}
I have read around about creating custom model binders, but all the tutorials are about binding a POCO class when we know the names of the query parameters / form values.
I just want to be able to tell the framework 'if parameter is strongly typed id type, instantiate using this constructor', so it will always work no matter what the name of the parameter is.
Can anyone point me in the right direction?
EDIT
This is the base class that the strongly typed ID's inherit from:
public class StronglyTypedId
{
private readonly string _id;
public StronglyTypedId(string id)
{
_id = id;
}
public static bool operator ==(StronglyTypedId a, StronglyTypedId b)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (((object)a != null) && ((object)b == null) || ((object)a == null))
{
return false;
}
return a._id == b._id;
}
public static bool operator !=(StronglyTypedId a, StronglyTypedId b)
{
return !(a == b);
}
public override string ToString()
{
return _id;
}
public override bool Equals(object obj)
{
if (!(obj is StronglyTypedId))
{
return false;
}
return ((StronglyTypedId)obj)._id == _id;
}
public Guid ToGuid()
{
return Guid.Parse(_id);
}
public bool HasValue()
{
return !string.IsNullOrEmpty(_id);
}
}
I figured out a way to do this just now using a custom model binder. This way will work no matter what the name of the parameter that needs to be bound:
public class JobIdModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(JobId))
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
string id = value.AttemptedValue;
return new JobId(id);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
So it's pretty much the same as if you are implementing a custom binder for a specific model type, except this is the bit of magic var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName) which means it will work regardless of naming of parameters.
Then you simply register the binder in your Global.cs like so:
ModelBinders.Binders.Add(typeof(JobId), new JobIdModelBinder());
I have a class:
public class Filter
{
public Filter (string name, string value)
{
Name = name;
Value = value;
}
public string Name {get; private set;}
public string Value {get; set;}
}
And a collection class:
public class FilterCollection : Collection<Filter>
{
// code elided
}
My component class:
public class MyComponent : Component
{
// ...
[Editor(typeof(FilterEditor), typeof(UITypeEditor))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public FilterCollection Filters { get; set; }
// ...
}
The problem is that the collection is not serialized correctly by the designer.
I'm sure I'm missing something but I don't know what.
ADDITIONAL INFO
What I would like the designer.cs file to have is something along the following:
myComponent.Filters.Add (new Filter ("some name", "some value"));
myComponent.Filters.Add (new Filter ("other name", "other value"));
Is this feasible?
Ok, problem solved.
I needed to use a TypeConverter for my Filter class:
internal class FilterConverter : TypeConverter
{
public override bool CanConvertTo (ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo (context, destinationType);
}
public override object ConvertTo (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor) && value is Filter)
{
ConstructorInfo constructor = typeof (Filter).GetConstructor (new[] {typeof (string), typeof (string)});
var filter = value as Filter;
var descriptor = new InstanceDescriptor (constructor, new[] {filter.Name, filter.Value}, true);
return descriptor;
}
return base.ConvertTo (context, culture, value, destinationType);
}
}
The converter is then added to the Filter class like so:
[TypeConverter(typeof(FilterConverter))]
public class Filter
{
// ...
}
The important thing to note here is the creation of an instance descriptor with the required parameters for the Filter constructor. Also, you need to set the last parameter (isComplete) of the InstanceDescriptor constructor to true.
Only the ConvertTo method gets called(a lot of times) when accessing the propertygrid. This correctly returns the "Foo!" string in the propertygrid. When I click to edit I get an exception Cannot convert object of type Foo to type System.String.(not exactly, translated). The ConvertFrom method doesn't get called, any clues why? And the error indicates it's trying to convert TO a string, not from.
I would think when I want to edit this object, it has to convert from Foo to string, and when finished editing back.
StringConverter class:
public class FooTypeConverter : StringConverter {
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
return new Foo((string) value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
return "Foo!";
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
return true;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return true;
}
}
Property being accessed:
Foo _foo = new Foo();
[Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(FooTypeConverter))]
public Foo Foo {
get {
return _foo;
}
set {
_foo = value;
}
}
Re your update; here's a FooEditor that should work as a shim:
class FooEditor : UITypeEditor
{
MultilineStringEditor ed = new MultilineStringEditor();
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
Foo foo = value as Foo;
if (foo != null)
{
value = new Foo((string)ed.EditValue(provider, foo.Value));
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return ed.GetEditStyle();
}
public override bool IsDropDownResizable {
get { return ed.IsDropDownResizable; }
}
}
You'll obviously need to associate it:
[TypeConverter(typeof(FooTypeConverter))]
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
class Foo { /* ... */ }
Cannot reproduce; it works fine for me; you should be testing the destinationType and type of value, btw - but that isn't stopping it calling ConvertFrom. Do you have a complete example (perhaps based on the following) that shows it not calling ConvertFrom?
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Forms;
public class FooTypeConverter : StringConverter {
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return new Foo("FooTypeConverter.ConvertFrom: " + Convert.ToString(value));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return "FooTypeConverter.ConvertTo: " + ((Foo)value).Value;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return true;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return true;
}
}
[TypeConverter(typeof(FooTypeConverter))]
class Foo
{
public string Value { get; set; }
public Foo(string value) { Value = value; }
public override string ToString()
{
return "Foo.ToString";
}
}
class Test
{
public Foo Foo { get; set; }
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
grid.SelectedObject = new Test { Foo = new Foo("Main") };
form.Controls.Add(grid);
Application.Run(form);
}
}
}
for auditory reasons I stores the arguments of the business methods serialized into the database using the binaryformatter.
The problem is that when an argument is a generic list I don't find the way to cast the deserialized object because I don't know the type, or If I will know the type I don't know how to cast the object at runtime.
Anybody knows how to cast an object containing a generic list dinamically at runtime?
I need to do this because I need to show the deserialized data in a property grid:
object objArg = bformatter.Deserialize(memStr);
//If the type is a clr type (int, string, etc)
if (objArg.GetType().Module.Name == "mscorlib.dll")
{
//If the type is a generic type (List<>, etc)
//(I'm only use List for these cases)
if (objArg.GetType().IsGenericType)
{
// here is the problem
pgArgsIn.SelectedObject = new { Value = objArg};
//In the previous line I need to do something like...
//new { Value = (List<objArg.GetYpe()>) objArg};
}
else
{
pgArgsIn.SelectedObject = new { Value = objArg.ToString() };
}
}
else
{
//An entity object
pgArgsIn.SelectedObject = objArg;
}
With BinaryFormatter you don't need to know the type; the metadata is included in the stream (making it bigger, but hey!). However, you can't cast unless you know the type. Often in this scenario you have to use common known interfaces (non-generic IList etc) and reflection. And lots of it.
I also can't think of a huge requirement to know the type to show in a PropertyGrid - since this accepts object, just give it what BinaryFormatter provides. Is there a specific issue you are seeing there? Again, you might want to check for IList (non-generic) - but it isn't worth worrying about IList<T>, since this isn't what PropertyGrid checks for!
You can of course find the T if you want (like so) - and use MakeGenericType() and Activator.CreateInstance - not pretty.
OK; here's a way using custom descriptors that doesn't involve knowing anything about the object or the list type; if you really want it is possible to expand the list items directly into the properties, so in this example you'd see 2 fake properties ("Fred" and "Wilma") - that is extra work, though ;-p
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public override string ToString() {
return Name;
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Person fred = new Person();
fred.Name = "Fred";
fred.DateOfBirth = DateTime.Today.AddYears(-23);
Person wilma = new Person();
wilma.Name = "Wilma";
wilma.DateOfBirth = DateTime.Today.AddYears(-20);
ShowUnknownObject(fred, "Single object");
List<Person> list = new List<Person>();
list.Add(fred);
list.Add(wilma);
ShowUnknownObject(list, "List");
}
static void ShowUnknownObject(object obj, string caption)
{
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
form.Text = caption;
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = ListWrapper.Wrap(obj);
Application.Run(form);
}
}
}
[TypeConverter(typeof(ListWrapperConverter))]
public class ListWrapper
{
public static object Wrap(object obj)
{
IListSource ls = obj as IListSource;
if (ls != null) obj = ls.GetList(); // list expansions
IList list = obj as IList;
return list == null ? obj : new ListWrapper(list);
}
private readonly IList list;
private ListWrapper(IList list)
{
if (list == null) throw new ArgumentNullException("list");
this.list = list;
}
internal class ListWrapperConverter : TypeConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(
ITypeDescriptorContext context, object value, Attribute[] attributes) {
return new PropertyDescriptorCollection(
new PropertyDescriptor[] { new ListWrapperDescriptor(value as ListWrapper) });
}
}
internal class ListWrapperDescriptor : PropertyDescriptor {
private readonly ListWrapper wrapper;
internal ListWrapperDescriptor(ListWrapper wrapper) : base("Wrapper", null)
{
if (wrapper == null) throw new ArgumentNullException("wrapper");
this.wrapper = wrapper;
}
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) {
throw new NotSupportedException();
}
public override bool CanResetValue(object component) { return false; }
public override bool IsReadOnly {get {return true;}}
public override void SetValue(object component, object value) {
throw new NotSupportedException();
}
public override object GetValue(object component) {
return ((ListWrapper)component).list;
}
public override Type ComponentType {
get { return typeof(ListWrapper); }
}
public override Type PropertyType {
get { return wrapper.list.GetType(); }
}
public override string DisplayName {
get {
IList list = wrapper.list;
if (list.Count == 0) return "Empty list";
return "List of " + list.Count
+ " " + list[0].GetType().Name;
}
}
}
}
If the serializer you are using does not retain the type - at the least, you must store the type of T along with the data, and use that to create the generic list reflectively:
//during storage:
Type elementType = myList.GetType().GetGenericTypeDefinition().GetGenericArguments[0];
string typeNameToSave = elementType.FullName;
//during retrieval
string typeNameFromDatabase = GetTypeNameFromDB();
Type elementType = Type.GetType(typeNameFromDatabase);
Type listType = typeof(List<>).MakeGenericType(new Type[] { elementType });
Now you have listType, which is the exact List<T> you used (say, List<Foo>). You can pass that type into your deserialization routine.