I am reading this http://msdn.microsoft.com/en-us/magazine/jj651572.aspx to learn mvvm light framework.
I download the source code Friend.cs.
My question is why some set method of different property are implemented differently.
For example, the setter for First name is, why I need 'ref' keyword for _firstName.
Set(FirstNamePropertyName, ref _firstName, value);
And the setter for DateOfBirthString is "
RaisePropertyChanged(() => DateOfBirth);
When will the linq expression will be evaluated?
namespace MyFriends.Model
{
[SimpleSerialize]
public class Friend : ObservableObject
{
/// <summary>
/// The <see cref="FirstName" /> property's name.
/// </summary>
public const string FirstNamePropertyName = "FirstName";
private string _firstName;
/// <summary>
/// Sets and gets the FirstName property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[SimpleSerialize(FieldName = "first_name")]
public string FirstName
{
get
{
return _firstName;
}
set
{
Set(FirstNamePropertyName, ref _firstName, value);
}
}
/// <summary>
/// The <see cref="LastName" /> property's name.
/// </summary>
public const string LastNamePropertyName = "LastName";
private string _lastName;
/// <summary>
/// Sets and gets the LastName property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[SimpleSerialize(FieldName = "last_name")]
public string LastName
{
get
{
return _lastName;
}
set
{
Set(LastNamePropertyName, ref _lastName, value);
}
}
/// <summary>
/// The <see cref="DateOfBirth" /> property's name.
/// </summary>
public const string DateOfBirthPropertyName = "DateOfBirth";
private string _dateOfBirthString;
/// <summary>
/// Sets and gets the DateOfBirth property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
[SimpleSerialize(FieldName = "birthday")]
public string DateOfBirthString
{
get
{
return _dateOfBirthString;
}
set
{
_dateOfBirthString = value;
RaisePropertyChanged(() => DateOfBirth);
}
}
public DateTime DateOfBirth
{
get
{
if (string.IsNullOrEmpty(_dateOfBirthString))
{
return DateTime.MinValue;
}
return DateTime.ParseExact(DateOfBirthString, "d", CultureInfo.InvariantCulture);
}
set
{
_dateOfBirthString = value.ToString("d", CultureInfo.InvariantCulture);
}
}
private string _imageUrl;
[SimpleSerialize(FieldName = "picture")]
public string ImageUrl
{
get
{
return _imageUrl;
}
set
{
_imageUrl = value;
RaisePropertyChanged(() => ImageUri);
}
}
public Uri ImageUri
{
get
{
return new Uri(_imageUrl);
}
}
}
}
The difference between those two methods is that the Set method replaces the old value of the _firstName field and then raises the PropertyChanged event, while the RaisePropertyChanged only raises the PropertyChanged event.
You'll want to use the Set method in most cases, since it helps to shorten property declarations by wrapping all that typically needs to be done within a property's setter in just one method:
It updates the value of the passed field and overwrites it with the content of value, and then
raises the PropertyChanged event to notify Views about this update.
The reason the field needs to be passed by reference (thus using ref _firstName) is that not the field's content is needed within the Set method, but the field itself is actually updated.
The RaisePropertyChanged method is useful when an update of one property does also affect additional properties. In this case, these properties' contents need to be updated manually, then the RaisePropertyChanged method can be called to inform Views about which properties have actually changed.
One instance I found that Set(ref _prop, value)
doesn't work is when you need to resend an update or need to do a custom comparison. Set(ref _prop, value)
will do a compare on the _prop and value for you but it may not be what you want. For some properties i may want to add some more conditions beyond just a comparison in which case I'll set the _prop = value manually and then raise the change right after;
But in most cases I use the Set(ref _prop, value)
Here is an actual usage, where the IsRunning is the primary operation, but it also raises a change notification on the property IsComplete which is based on IsRunning. This way both properties send a notify without undo extra coding.
private bool _IsRunning;
public bool IsRunning
{
get => _IsRunning;
set
{
SetProperty(ref _IsRunning, value);
RaisePropertyChanged("IsComplete");
}
}
public bool IsComplete => !IsRunning;
Related
I have moved xamrin.form.maps to MVVM and have set my xaml to
<viewModels:CustomMap IsShowingUser="True" x:Name="customMap" MapPosition="{Binding MyPosition}" CustomPins="{Binding PinCollection}" />
My customMap Class
public static readonly BindableProperty MapPositionProperty = BindableProperty.Create(
nameof(MapPosition),
typeof(Position),
typeof(CustomMap),
new Position(0, 0),
propertyChanged: (b, o, n) =>
{
Console.WriteLine("here");
((CustomMap)b).MoveToRegion(MapSpan.FromCenterAndRadius(
(Position)n, Distance.FromMiles(.05)));
});
public Position MapPosition
{
get { return (Position)GetValue(MapPositionProperty); }
set { SetValue(MapPositionProperty, value); }
}
From page one, I am sending a lat lng to the map. If I set MapPosition in my constructor in my VM it works perfect, the problem is getting the lat and lng to my constructor before everything is bound, other wise it will not update.
In the view model I am collecting the parameters to set the position.
public override async void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("mapLocation"))
{
var mapLocation = parameters.GetValue<MapLocation>("mapLocation");
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
}
}
This has nothing to do with the time when your property is updated.
You bound MapPosition to MyPosition, which I'd guess is a property. For MVVM to work, this MapPosition has to notify that it has changed, usually this looks something like (the the documentation of INotifyPropertyChanged.PropertyChanged)
public MapLocation MyPosition
{
get => myPosition;
set
{
if(myPosition == value)
{
return;
}
myPosition = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string memberName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Your view does know that if the bound objects implements INotifyPropertyChanged it has to subscribe to PropertyChanged and can update its state accordingly if this event is raised.
Now why doesn't it work in your case?
You are not setting MyPosition, allowing the setter to raise PropertyChanged and thus the view to update its state, but you are directly setting the backing field myPosition. In this case, the setter never is called and the view has no chance to know that the value of MyPosition has changed, therefor won't update its state.
If your setter implements the pattern shown above, just change
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
to
MyPosition = new Position(mapLocation.Lat, mapLocation.Lng);
and it should work. Otherwise implement the change notification pattern in MyPosition, too.
How can I be notified when ProgressBar's Value changes with .NET UIAutomation framework? I dont see such property in AutomationElement class.
I drew this sample directly from the MSDN documentation, changing only the property:
AutomationPropertyChangedEventHandler propChangeHandler;
/// <summary>
/// Adds a handler for property-changed event; in particular, a change in the value
/// </summary>
/// <param name="element">The UI Automation element whose state is being monitored.</param>
public void SubscribePropertyChange(AutomationElement element)
{
Automation.AddAutomationPropertyChangedEventHandler(element,
TreeScope.Element,
propChangeHandler = new AutomationPropertyChangedEventHandler(OnPropertyChange),
ValuePattern.ValueProperty);
}
/// <summary>
/// Handler for property changes.
/// </summary>
/// <param name="src">The source whose properties changed.</param>
/// <param name="e">Event arguments.</param>
private void OnPropertyChange(object src, AutomationPropertyChangedEventArgs e)
{
AutomationElement sourceElement = src as AutomationElement;
if (e.Property == ValuePattern.ValueProperty)
{
// TODO: Do something with the new value.
// The element that raised the event can be identified by its runtime ID property.
}
else
{
// TODO: Handle other property-changed events.
}
}
public void UnsubscribePropertyChange(AutomationElement element)
{
if (propChangeHandler != null)
{
Automation.RemoveAutomationPropertyChangedEventHandler(element, propChangeHandler);
}
}
I have a scenario where I am using Entity Framework in a WCF service, and changes happen on a non-tracked instance of a type that is mapped back to the database via code-first (non-trivial updates and deletes throughout the instance's object tree). When I try to attach the non-tracked instance into the context, EF is only recognizing changes to the simple value types on the root object.
Does anyone know of an elegant solution for this scenario? I am looking for a way to do this by using a generic repository, and avoiding having to run through the instance's entire object tree managing the "attach/detach" state of every object. I have considered possibly using ValueInjecter or AutoMapper to run the changes on a fully hydrated and tracked instance of the "old" state in order for the context to pickup the changes. Also, how would Nhibernate handle this situation?
Thanks in advance for your input!
UPDATE (7/31/2012): I have updated the code to handle genericly-typed keys, and some typing issues with EF Proxies. Also added some helper extensions when dealing with IEntity types. This implementation isn't perfect, but it is very functional.
UPDATE (3/13/2012): I have added a feature request for cleaner merging in EF. The request is located here: http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking
UPDATE (3/12/2012): I have posted my solution below. It uses FubuCore, ValueInjecter, and requires entities to be marked with one of two interfaces, either IEntity, or IRecursiveEntity for recursive classes. The solution will handle recursive, self-linked entities.
Also, I am referencing a generic repository (Repository) that allows me to get a reference to the IDbSet that EF exposes. This could be substituded with any other generic or specific repository. Lastly, the IEntity interface uses an int? id, however you could define that however you want (Guid/Guid?). The solution itself isn't quite as elegant as I would like, however it allows for much more elegant data access code when behind a physical WCF service boundary.
public class DomainMergeInjection : ConventionInjection
{
private readonly Repository _repository;
private readonly Dictionary<string, object> _potentialParentObjectDump;
private readonly Cache<Type, Type> _entityTypesAndKeysCache;
public DomainMergeInjection(Repository repository)
{
_repository = repository;
_potentialParentObjectDump = new Dictionary<string, object>();
_entityTypesAndKeysCache = new Cache<Type, Type>();
}
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if(c.SourceProp.Value == null)
return null;
//for value types and string just return the value as is
if(c.SourceProp.Type.IsSimple())
return c.SourceProp.Value;
//TODO: Expand on this to handle IList/IEnumerable (i.e. the non-generic collections and arrays).
//handle arrays
if(c.SourceProp.Type.IsArray)
{
var sourceArray = c.SourceProp.Value as Array;
// ReSharper disable PossibleNullReferenceException
var clonedArray = sourceArray.Clone() as Array;
// ReSharper restore PossibleNullReferenceException
for(int index = 0; index < sourceArray.Length; index++)
{
var sourceValueAtIndex = sourceArray.GetValue(index);
//Skip null and simple values that would have already been moved in the clone.
if(sourceValueAtIndex == null || sourceValueAtIndex.GetType().IsSimple())
continue;
// ReSharper disable PossibleNullReferenceException
clonedArray.SetValue(RetrieveComplexSourceValue(sourceValueAtIndex), index);
// ReSharper restore PossibleNullReferenceException
}
return clonedArray;
}
//handle IEnumerable<> also ICollection<> IList<> List<>
if(c.SourceProp.Type.IsGenericEnumerable())
{
var t = c.SourceProp.Type.GetGenericArguments()[0];
if(t.IsSimple())
return c.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
dynamic list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach(var sourceItem in (IEnumerable)c.SourceProp.Value)
{
addMethod.Invoke(list, new[] { RetrieveComplexSourceValue(sourceItem) });
}
return list;
}
//Get a source value that is in the right state and is tracked if needed.
var itemStateToInject = RetrieveComplexSourceValue(c.SourceProp.Value);
return itemStateToInject;
}
private object RetrieveComplexSourceValue(object source)
{
//If the source is a non-tracked type, or the source is a new value, then return its value.
if(!source.ImplementsIEntity(_entityTypesAndKeysCache) || source.IsEntityIdNull(_entityTypesAndKeysCache))
return source;
object sourceItemFromContext;
//Handle recursive entities, this could probably be cleaned up.
if(source.ImplementsIRecursiveEntity())
{
var itemKey = source.GetEntityIdString(_entityTypesAndKeysCache) + " " + ObjectContext.GetObjectType(source.GetType());
//If we have a context item for this key already, just return it. This solves a recursion problem with self-linking items.
if(_potentialParentObjectDump.ContainsKey(itemKey))
return _potentialParentObjectDump[itemKey];
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Add the class into the object dump in order to avoid any infinite recursion issues with self-linked objects
_potentialParentObjectDump.Add(itemKey, sourceItemFromContext);
}
else
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Recursively use this injection class instance to inject the source state on to the context source state.
var itemStateToInject = sourceItemFromContext.InjectFrom(this, source);
return itemStateToInject;
}
private object GetSourceItemFromContext(object source)
{
if(source == null)
return null;
//Using dynamic here to "AutoCast" to an IEntity<>. We should have one, but it's important to note just in case.
dynamic sourceEntityValue = source;
var sourceEntityType = ObjectContext.GetObjectType(source.GetType());
var sourceKeyType = sourceEntityType.GetEntityKeyType();
var method = typeof(DomainMergeInjection).GetMethod("GetFromContext", BindingFlags.Instance | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(sourceEntityType, sourceKeyType);
var sourceItemFromContext = generic.Invoke(this, new object[] { new object[] { sourceEntityValue.Id } });
return sourceItemFromContext;
}
// ReSharper disable UnusedMember.Local
private TItem GetFromContext<TItem, TKey>(object[] keys) where TItem : class, IEntity<TKey>
// ReSharper restore UnusedMember.Local
{
var foundItem = _repository.GetDbSet<TItem>().Find(keys);
return foundItem;
}
}
public static class EntityTypeExtensions
{
/// <summary>
/// Determines if an object instance implements IEntity.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache">A cache to hold types that do implement IEntity. If the cache does not have the Type and the Type does implement IEntity, it will add the type to the cache along with the </param>
/// <returns></returns>
public static bool ImplementsIEntity(this object entity, Cache<Type, Type> entityCache = null)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityCache != null && entityCache.Has(entityType))
return true;
var implementationOfIEntity = entityType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntity<>));
if(implementationOfIEntity == null)
return false;
if(entityCache != null)
{
var keyType = implementationOfIEntity.GetGenericArguments()[0];
entityCache.Fill(entityType, keyType);
}
return true;
}
/// <summary>
/// Determines if an object instances implements IRecurisveEntity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static bool ImplementsIRecursiveEntity(this object entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
var implementsIRecursiveEntity = entityType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRecursiveEntity<>));
return implementsIRecursiveEntity;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static bool IsEntityIdNull(this object entity, Cache<Type, Type> entityCache = null)
{
bool isEntityIdNull = ExecuteEntityIdMethod<bool>("IsEntityIdNull", entity, entityCache);
return isEntityIdNull;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static string GetEntityIdString(this object entity, Cache<Type, Type> entityCache = null)
{
string entityIdString = ExecuteEntityIdMethod<string>("GetEntityIdString", entity, entityCache);
return entityIdString;
}
private static T ExecuteEntityIdMethod<T>(string methodName, object entityInstance, Cache<Type, Type> entityCache = null)
{
if(!entityInstance.ImplementsIEntity(entityCache))
throw new ArgumentException(string.Format("Parameter entity of type {0} does not implement IEntity<>, and so ist not executable for {1}!", entityInstance.GetType(), methodName));
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entityInstance.GetType());
var keyType = entityCache != null ? entityCache[entityType] : entityType.GetEntityKeyType();
var method = typeof(EntityTypeExtensions).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(keyType);
T returnValue = (T)generic.Invoke(null, new[] { entityInstance });
return returnValue;
}
private static string GetEntityIdString<TKey>(IEntity<TKey> entity)
{
var entityIdString = entity.Id.ToString();
return entityIdString;
}
private static bool IsEntityIdNull<TKey>(IEntity<TKey> entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityType.IsPrimitive)
return false;
//NOTE: We know that this entity's type is NOT primitive, therefore we can cleanly test for null, and return properly.
// ReSharper disable CompareNonConstrainedGenericWithNull
var entityIdIsNull = entity.Id == null;
// ReSharper restore CompareNonConstrainedGenericWithNull
return entityIdIsNull;
}
public static Type GetEntityKeyType(this Type typeImplementingIEntity)
{
var implementationOfIEntity = typeImplementingIEntity.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>));
if(implementationOfIEntity == null)
throw new ArgumentException(string.Format("Type {0} does not implement IEntity<>", typeImplementingIEntity));
var keyType = implementationOfIEntity.GetGenericArguments()[0];
return keyType;
}
}
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
public interface IRecursiveEntity<TKey> : IEntity<TKey>
{
IRecursiveEntity<TKey> Parent { get; }
IEnumerable<IRecursiveEntity<TKey>> Children { get; }
}
you could use the detached object only as a DTO,
and after refill the object from context with values from the DTO
with ValueInjecter this would be:
//manually
conObj.InjectFrom(dto);
conObj.RefTypeProp.InjectFrom(dto.RefTypeProp);
...
//or by writing a custom injection:
conObj.InjectFrom<ApplyChangesInjection>(dto);
here's the Injection that will do that automatically, (I did it by modifying a bit the DeepClone Injection from VI's home page)
the trick here is that the Injection uses itself in the SetValue method
public class ApplyChangesInjection : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if (c.SourceProp.Value == null) return null;
//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
return c.SourceProp.Value;
//handle arrays - not impl
//handle IEnumerable<> also ICollection<> IList<> List<> - not impl
//for simple object types apply the inject using the corresponding source
return c.TargetProp.Value
.InjectFrom<ApplyChangesInjection>(c.SourceProp.Value);
}
}
//Note: I'm not handling collections in this injection, I just wanted you to understand the principle,
you can look at the original http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home
I have an application in MVVM style. There is a custom control in there with a dependency property holding an ObservableCollection of A. A has a ObservableCollection of B. A and B implement INotifyPropertyChanged.
When I add an object of B to A in my ViewModel, the changes will not be propagated to the control. The binding in xaml is correct, because the Bs are displayed and Mode is TwoWay.
This is normal behaviour, as the control only listens on INotifyPropertyChanged and not on ICollectionChanged. The collection property itself does not change and, therefore, the control does not know that it has to refresh.
In order to pass the collection changes to the control you will have to subscribe to the CollectionChanged event of you ObservableCollection, and then raise the the property changed event for collection property when the collection is changed (items added, removed, moved, or the collection is cleared).
#region [BViewModelCollection]
/// <summary>
/// The <see cref="BViewModelCollection" /> property's name.
/// </summary>
public const string BViewModelCollectionPropertyName = "BViewModelCollection";
private ObservableCollection<BViewModel> _bViewModelCollection = new ObservableCollection<BViewModel>();
/// <summary>
/// Gets the BViewModelCollection property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public ObservableCollection<BViewModel> BViewModelCollection {
get {
return _bViewModelCollection;
}
set {
if (_bViewModelCollection != value) {
SetBViewModelCollection(value);
RaisePropertyChanged(BViewModelCollectionPropertyName);
}
}
}
private void SetBViewModelCollection(ObservableCollection<BViewModel> value) {
if (_bViewModelCollection != null)
_bViewModelCollection.CollectionChanged -= this.BViewModelCollection_CollectionChanged;
_bViewModelCollection = value;
if (_bViewModelCollection != null)
_bViewModelCollection.CollectionChanged += this.BViewModelCollection_CollectionChanged;
}
private void BViewModelCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
RaisePropertyChanged(BViewModelCollectionPropertyName);
}
#endregion
You can now use the SetBViewModelCollection to set the ObservableCollection and automatically register the events correctly, without raising a PropertyChanged event for the collection - e.g. for usage in the constructor or a load data method.
When an item is now added to or removed from the collection your control should get notified that the collection propterty has been changed.
Note: Adjust the method names to your own framework.
I'm trying to migrate to ASP.Net MVC 2 and meet some issues.
Here is one :
I needed to bind directly a Dictionary as result of a view post.
In ASP.Net MVC 1 it worked perfectly using a custom IModelBinder :
/// <summary>
/// Bind Dictionary<int, int>
///
/// convention : <elm name="modelName_key" value="value"></elm>
/// </summary>
public class DictionaryModelBinder : IModelBinder
{
#region IModelBinder Members
/// <summary>
/// Mandatory
/// </summary>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
IDictionary<int, int> retour = new Dictionary<int, int>();
// get the values
var values = bindingContext.ValueProvider;
// get the model name
string modelname = bindingContext.ModelName + '_';
int skip = modelname.Length;
// loop on the keys
foreach(string keyStr in values.Keys)
{
// if an element has been identified
if(keyStr.StartsWith(modelname))
{
// get that key
int key;
if(Int32.TryParse(keyStr.Substring(skip), out key))
{
int value;
if(Int32.TryParse(values[keyStr].AttemptedValue, out value))
retour.Add(key, value);
}
}
}
return retour;
}
#endregion
}
It worked in pair with some smart HtmlBuilder that displayed dictionary of data.
The problem I meet now is that ValueProvider is not a Dictionary<> anymore, it's a IValueProvider that only allow to get values whose name is known
public interface IValueProvider
{
bool ContainsPrefix(string prefix);
ValueProviderResult GetValue(string key);
}
This is really not cool as I cannot perform my smart parsing...
Question :
Is there another way to get all keys ?
Do you know another way to bind a collection of HTML elements to a Dictionary
Thanks for your suggestions
O.
Though this question has been marked 'answered' I think the following may be helpful.
I had the same problem and had a look at the source code of the System.Web.Mvc.DefaultValueProvider. It gets its values from the RouteData, the query string or from a request form submission (in that exact order). To collect all the keys (which is what you ask for in your first question) I wrote the following helper method.
private static IEnumerable<string> GetKeys(ControllerContext context)
{
List<string> keys = new List<string>();
HttpRequestBase request = context.HttpContext.Request;
keys.AddRange(((IDictionary<string,
object>)context.RouteData.Values).Keys.Cast<string>());
keys.AddRange(request.QueryString.Keys.Cast<string>());
keys.AddRange(request.Form.Keys.Cast<string>());
return keys;
}
You can use this method to enumerate over the keys:
foreach (string key in GetKeys(controllerContext))
{
// Do something with the key value.
}
I don't think you'll be able to do it this way anymore in MVC 2.
Alternatively, you could extend DefaultModelBinder and override one of its virtual methods like GetModelProperties and then change the ModelName inside the ModelBindingContext. Another option would be to implement a custom MetadataProvider for your Dictionary type, you can change the model name there as well.