I'm using a bindingsource to fill a form from Nhibernate list:
public class Customer{
public string Name { get; set;}
public IList<Order> Orders { get; set;}
}
bindingSourceCustomer.DataSource = session.Query<Customer>().ToList();
bindingSourceOrder.DataSource = bindingSourceCustomer;
bindingSourceOrder.DataMember = "Orders";
now when I call
bindingSourceOrder.AddNew();
an exception is thrown:
The value "System.Object" is not of type "Model.Order" and cannot be
used in this generic collection.
Now I changed the first line to:
bindingSourceCustomer.DataSource = session.Query<Customer>().Select(customer =>
{
customer.Orders = customer.Orders.ToList();
return customer;
})
.ToList();
it worked, the reason why, is because Nhibernate uses the PersistentBag as an implementation of IList, which apparently doesn't work well with binding source (As far as I see).
Any suggestion either how to make Nhibernate return List class, or how to solve the problem with binding source?
That's because the BindingSource is unable to discover the list type:
NHibernate's persistent bag has not the ITypedList interface nor the Indexer Property public.
You need to replace NHibernate CollectionTypeFactory with a custom one, before adding mappings.
I attach my implementation:
PersistentGenericBag:
public class EnhancedPersistentGenericBag<T> : PersistentGenericBag<T> , ITypedList
{
public EnhancedPersistentGenericBag(ISessionImplementor session, ICollection<T> coll) : base(session, coll) { }
public EnhancedPersistentGenericBag(ISessionImplementor session) : base(session) { }
public EnhancedPersistentGenericBag() { }
public new T this[int index]
{
get
{
return (T)base[index];
}
set
{
base[index] = value;
}
}
public string GetListName(PropertyDescriptor[] listAccessors) { return GetType().Name; }
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
return TypeDescriptor.GetProperties(typeof(T));
}
}
CollectionTypeFactory:
public class EnhancedCollectionTypeFactory : DefaultCollectionTypeFactory
{
public override CollectionType Bag<T>(string role, string propertyRef, bool embedded)
{
return new EnhancedGenericBagType<T>(role, propertyRef);
}
}
GenericBagType:
public class EnhancedGenericBagType<T> : BagType
{
public EnhancedGenericBagType(string role, string propertyRef) :
base(role, propertyRef, false) { }
public override IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister, object key)
{
return new EnhancedPersistentGenericBag<T>(session);
}
public override IPersistentCollection Wrap(ISessionImplementor session, object collection)
{
return new EnhancedPersistentGenericBag<T>(session, (ICollection<T>)collection);
}
public override Type ReturnedClass
{
get
{
return typeof(ICollection<T>);
}
}
protected override void Add(object collection, object element)
{
((ICollection<T>)collection).Add((T)element);
}
protected override void Clear(object collection)
{
((ICollection<T>)collection).Clear();
}
public override object Instantiate(int anticipatedSize)
{
if (anticipatedSize > 0)
return new List<T>(anticipatedSize + 1);
else
return new List<T>();
}
}
How to override default CollectionTypeFactory:
Configuration cfg = new Configuration();
cfg.CollectionTypeFactory<EnhancedCollectionTypeFactory>();
Related
I posted a more detailed version of this question on code review exchange, check this link. I later got my implementation reviewed by one of my colleagues and he suggested a different approach to solve the problem. So this question is to help me find out, which of the two approaches should be picked and why?
If this is not suitable for StackOverflow and should still be in CodeReviewExchange, please let me know and I will move it there.
Also I want to say this in the beginning itself because I was criticized for this in my earlier post. The actual code is different than what I have provided here, its more complex so I had to simplify it to ask just what I want to figure out i.e. inheritance vs composition, which is better here?
Here is my set of classes:
public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
public virtual bool QuotedValues { get; set; } = true;
public abstract string OperatorSymbol { get; }
public string BuildCondition(SearchCondition searchCondition)
{
var conditionBuilder = new StringBuilder();
var context = searchCondition.GetContext<TContext>();
conditionBuilder.Append(context.FieldId);
conditionBuilder.Append(OperatorSymbol);
conditionBuilder.Append(GetValue(context));
return conditionBuilder.ToString();
}
public abstract bool CanHandle(FilterAction filterAction);
public abstract object GetValue(TContext context);
}
public class TextLikeConditionBuilder : ConditionBuilder<TextContext>
{
public override string OperatorSymbol => " LIKE ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextLike;
public override object GetValue(TextContext context)
{
if (context.Text == null)
{
return null;
}
return string.Concat("%", context.Text, "%");
}
}
public class TextEqualsConditionBuilder : ConditionBuilder<TextContext>
{
public override string OperatorSymbol => " = ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextEqual;
public override object GetValue(TextContext context)
{
return context.Text;
}
}
In their default implementation the classes build a WHERE clause for SQL. These classes are consumed by other developers and can be overridden to build WHERE clause that is not necessarily meant for Database for example one can override protected virtual StringBuilder GetCondition(SearchFilterCondition filterCondition, TContext context) to build a QUERY for Elasticsearch also.
Coming back to the topic, in one particular instance where these classes are used with their default behavior, I wanted to delimit the FieldId so as to ensure that it works when FieldId contains spaces and special characters. This is how I implemented the solution:
public interface IDelimitedIdentifier
{
string Delimit(string input);
}
internal class SqlServerDelimitedIdentifier : IDelimitedIdentifier
{
public string Delimit(string input)
{
return "[" + input.Replace("]", "]]") + "]";
}
}
internal class OracleDelimitedIdentifier : IDelimitedIdentifier
{
public string Delimit(string input)
{
return "\"" + input + "\"";
}
}
public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
public virtual bool QuotedValues { get; set; } = true;
public abstract string OperatorSymbol { get; }
public string BuildCondition(SearchCondition searchCondition)
{
var conditionBuilder = new StringBuilder();
var context = searchCondition.GetContext<TContext>();
conditionBuilder.Append(SanitizeFieldId(context.FieldId));
conditionBuilder.Append(OperatorSymbol);
conditionBuilder.Append(GetValue(context));
return conditionBuilder.ToString();
}
public abstract bool CanHandle(FilterAction filterAction);
public abstract object GetValue(TContext context);
protected virtual string SanitizeFieldId(string fieldId)
{
return _delimitedIdentifier.Delimit(fieldId);
}
}
public class SanitizedFieldConditionBuilder<TContext> : ConditionBuilder<TContext> where TContext : FieldSearchContextBase
{
private readonly ConditionBuilder<TContext> _baseConditionBuilder;
private readonly IDelimitedIdentifier _delimitedIdentifier;
public SanitizedFieldConditionBuilder(ConditionBuilder<TContext> baseConditionBuilder, IDelimitedIdentifier delimitedIdentifier)
{
QuotedValues = false;
_baseConditionBuilder = baseConditionBuilder;
_delimitedIdentifier = delimitedIdentifier;
}
public override string OperatorSymbol => _baseConditionBuilder.OperatorSymbol;
public override bool CanHandle(SearchFilterAction action) => _baseConditionBuilder.CanHandle(action);
public override object GetValue(TContext context) => _baseConditionBuilder.GetValue(context);
protected override string SanitizeFieldId(string fieldId)
{
return _delimitedIdentifier.Delimit(fieldId);
}
}
public static class ConditionBuilderExtensions
{
public static SanitizedFieldConditionBuiler<TContext> SanitizeField<TContext>(this ConditionBuilder<TContext> source, IColumnSanitizer columnSanitizer) where TContext : FieldSearchContext
{
return new SanitizedFieldConditionBuiler<TContext>(source, columnSanitizer);
}
public static ParameterizedConditionBuilder<TContext> WithParameters<TContext>(this ConditionBuilder<TContext> source,
ParameterCollection parameterCollection, bool parameterizeNullValues = false) where TContext : FieldSearchContext
{
return new ParameterizedConditionBuilder<TContext>(source, parameterCollection, parameterizeNullValues);
}
}
The classes can be instantiated as shown below:
class Program
{
static void Main(string[] args)
{
var conditionBuilders = new List<IConditionBuilder>()
{
new TextEqualsConditionBuilder().SanitizeField(new SqlServerDelimitedIdentifier()),
new TextLikeConditionBuilder().SanitizeField(new SqlServerDelimitedIdentifier())
};
}
}
My colleague suggested to use composition instead of extension. Here is how the classes would look like as per his recommendation.
public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
private readonly IDelimitedIdentifier DelimitedIdentifier;
public virtual bool QuotedValues { get; set; } = true;
public abstract string OperatorSymbol { get; }
protected ConditionBuilder(IDelimitedIdentifier delimitedIdentifier)
{
DelimitedIdentifier = delimitedIdentifier;
}
public string BuildCondition(SearchCondition searchCondition)
{
var conditionBuilder = new StringBuilder();
var context = searchCondition.GetContext<TContext>();
conditionBuilder.Append(DelimitedIdentifier.Delimit(context.FieldId));
conditionBuilder.Append(OperatorSymbol);
conditionBuilder.Append(GetValue(context));
return conditionBuilder.ToString();
}
public abstract bool CanHandle(FilterAction filterAction);
public abstract object GetValue(TContext context);
}
public class TextLikeConditionBuilder : ConditionBuilder<TextContext>
{
public TextLikeConditionBuilder(IDelimitedIdentifier delimitedIdentifier) : base(delimitedIdentifier)
{
}
public override string OperatorSymbol => " LIKE ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextLike;
public override object GetValue(TextContext context)
{
if (context.Text == null)
{
return null;
}
return string.Concat("%", context.Text, "%");
}
}
public class TextEqualsConditionBuilder : ConditionBuilder<TextContext>
{
public TextEqualsConditionBuilder(IDelimitedIdentifier delimitedIdentifier) : base(delimitedIdentifier)
{
}
public override string OperatorSymbol => " = ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextEqual;
public override object GetValue(TextContext context)
{
if (context.Text == null)
{
return null;
}
return context.Text;
}
}
Here are my arguments for why it shouldn't be done as per what my colleague recommends doing:
IDelimitedIdentifier is very specific to database, why should it be moved inside the base class when the functionality can be supported using extension.
Extension reduces the number of changes otherwise adding that constructor would mean changing all derived classes and let me tell you that there are 15 odd deriving classes
Isn't adding a constructor against Open-Closed principle?
EDIT: Since so many people are concerned about the SQL Injection issue here, rather than answering what I asked, I am adding this additional code that takes care of it. I have also updated the ConditionBuilderExtensions class defined above.
public class ParameterizedConditionBuilder<TContext> : ConditionBuilder<TContext>
where TContext : FieldSearchContext
{
private readonly ConditionBuilder<TContext> baseConditionBuilder;
private readonly ParameterCollection parameterCollection;
private readonly bool parameterizeNullValues;
public override bool QuotedValues { get; set; } = false;
public ParameterizedConditionBuilder(ConditionBuilder<TContext> baseConditionBuilder,
ParameterCollection parameterCollection, bool parameterizeNullValues = false)
{
this.baseConditionBuilder = baseConditionBuilder;
this.parameterCollection = parameterCollection;
this.parameterizeNullValues = parameterizeNullValues;
}
public override bool CanHandle(FilterAction action) => baseConditionBuilder.CanHandle(action);
public override string OperatorSymbol => baseConditionBuilder.OperatorSymbol;
public override object GetValue(TContext context)
{
object val = baseConditionBuilder.GetValue(context);
if (val == null && !parameterizeNullValues)
{
return null;
}
string p = parameterCollection.AddParameter(val);
return p;
}
}
Here is how I build my conditions and then use them in DBCommand. Its immune to SQL Injection
private static DbCommand CreateDbCommand(Database database, MergeOperation mergeOperation, IList<SearchCondition> searchCondition)
{
var whereConditions = new List<string>();
ParameterCollection paramCollection = new ParameterCollection("{{{0}}}");
var conditionBuilders = new List<IConditionBuilder>()
{
new TextEqualsConditionBuilder().WithParameters(paramCollection).SanitizeField(new SqlSanitizer()),
new TextLikeConditionBuilder().WithParameters(paramCollection).SanitizeField(new SqlSanitizer())
};
foreach (var condition in searchCondition)
{
var context = condition.GetContext<FieldSearchContext>();
var conditionBuilder = conditionBuilders.FirstOrDefault(u => u.CanHandle(condition.FilterAction));
whereConditions.Add(conditionBuilder.BuildCondition(condition));
}
SqlBuilder sqlBuilder = new SqlBuilder();
sqlBuilder.SELECT("Id");
sqlBuilder.FROM("Students");
if (whereConditions.Count > 0)
{
sqlBuilder.WHERE(string.Join(Convert.ToString(" " + mergeOperation + " "), whereConditions), paramCollection.GetParameters().Select(pair => pair.Value).ToArray());
}
DbExtensions.Database sqlBuilderDb = new DbExtensions.Database(database.CreateConnection());
IDbCommand sqlBuilderCommand = sqlBuilderDb.CreateCommand(sqlBuilder);
DbCommand databaseCommand = database.GetSqlStringCommand(sqlBuilderCommand.CommandText);
if (sqlBuilderCommand.Parameters != null)
{
foreach (IDataParameter dataParameter in sqlBuilderCommand.Parameters)
{
database.AddInParameter(databaseCommand, dataParameter.ParameterName, dataParameter.DbType, dataParameter.Value);
}
}
return databaseCommand;
}
SqlBuilder is from a nuget package DBExtensions. I use Microsoft.Enterprise.Library.Data for database access.
EDIT 2: Removed the code that was subjected to SQL Injection
I made a small custom configuration setting and I keep getting the error "The entry '' has already been added." when I try to use my custom collection. My code looks like this.
The issue comes from my tag.
I don't see what I am missing since I have the same thing implemented for and this one works perfectly.
My .NET version is 4.0 if that helps.
The app config section in question:
<WorkersCollectionSection>
<WorkersList>
<Worker name="Category" isEnabled="false"
assemblyNamespace="xxxxxx.xxxxxxxxxxxxxxx.Models.Category"
queueName="CategorQueue"
saveToFolder="false">
<HandlesList>
<Handle name="xxxxxxx" isEnabled="true"/>
<Handle name="yyyyyyy" isEnabled="true"/>
<Handle name="zzzzzzzzzz" isEnabled="true"/>
</HandlesList>
</Worker>
<WorkersList>
<WorkersCollectionSection>
The definition of the property:
[ConfigurationProperty("HandlesList")]
[ConfigurationCollection(typeof(WorkerCollection), AddItemName = "Handle")]
public HandleCollection HandleCollection
{
get { return (HandleCollection) base["HandlesList"]; }
}
The code for the tag :` public class HandleCollection : ConfigurationElementCollection, IEnumerable
{
public HandleCollection()
{
HandleElement handle = (HandleElement)CreateNewElement();
BaseAdd(handle);
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
}
protected override ConfigurationElement CreateNewElement()
{
return new HandleElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((HandleElement)element).Name;
}
public HandleElement this[int index]
{
get { return (HandleElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
public new HandleElement this[string Name]
{
get { return (HandleElement)BaseGet(Name); }
}
public int IndexOf(HandleElement handle)
{
return BaseIndexOf(handle);
}
public void Add(HandleElement url)
{
BaseAdd(url);
}
protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}
public void Remove(HandleElement handle)
{
if (BaseIndexOf(handle) >= 0)
BaseRemove(handle);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(string name)
{
BaseRemove(name);
}
public void Clear()
{
BaseClear();
}
IEnumerator<ConfigurationElement> IEnumerable<ConfigurationElement>.GetEnumerator()
{
return (from i in Enumerable.Range(0, this.Count)
select this[i])
.GetEnumerator();
}
protected override string ElementName
{
get { return "Handle"; }
}
public static explicit operator HandleCollection(Dictionary<string, string> v)
{
throw new NotImplementedException();
}
public static explicit operator HandleCollection(ConfigurationSection v)
{
throw new NotImplementedException();
}
}`
Code for the handle elements inside the list:
public class HandleElement: ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
[StringValidator(InvalidCharacters = "~!##$%^&*()[]{}/;'\"|\\", MinLength = 0, MaxLength = 60)]
public string Name
{
get { return base["name"] as string; }
set { base["name"] = value; }
}
[ConfigurationProperty("isEnabled", IsRequired = true)]
public bool IsEnabled
{
get { return (bool)base["isEnabled"]; }
set { base["isEnabled"] = value; }
}
}
It seems that the issue was that I did not put a check in the collection adding method to check if the element I am adding has a different key than the empty one. I changed my collection constructor to :
public HandleCollection()
{
HandleElement handle = (HandleElement)CreateNewElement();
if (handle.Name != "")
BaseAdd(handle);
}
Now it seems to work fine.
I need to implement automatic UI Tests for a Delphi Application with Visual Studio Coded UI Tests. I have already implemented the IAccessible Interface to my Delphi-Contols. It works fine and i get the AccessibleName from the Control.
Then i implemented an extension for visual studio. In this extension i have my own PropertyProvider-, ExtensionPackage- and WinControl-Class.
PropertyProvider:
namespace CUITExtension
{
public class AccessibleNamePropertyProvider : UITestPropertyProvider
{
private static Dictionary<string, UITestPropertyDescriptor> accessibleNamePropertyMap = null;
private static Dictionary<string, UITestPropertyDescriptor> AccessibleNamePropertyMap
{
get
{
if (accessibleNamePropertyMap == null)
{
UITestPropertyAttributes read = UITestPropertyAttributes.Readable
| UITestPropertyAttributes.DoNotGenerateProperties;
accessibleNamePropertyMap = new Dictionary<string, UITestPropertyDescriptor>
(StringComparer.OrdinalIgnoreCase);
accessibleNamePropertyMap.Add("AccessibleName", new UITestPropertyDescriptor(typeof(string), read));
}
return accessibleNamePropertyMap;
}
}
public override UITestPropertyDescriptor GetPropertyDescriptor(UITestControl uiTestControl, string propertyName)
{
return AccessibleNamePropertyMap[propertyName];
}
public override ICollection<string> GetPropertyNames(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
{
// the keys of the property map are the collection of property names
return AccessibleNamePropertyMap.Keys;
}
throw new NotSupportedException();
}
public override object GetPropertyValue(UITestControl uiTestControl, string propertyName)
{
if (String.Equals(propertyName, "AccessibleName", StringComparison.OrdinalIgnoreCase))
{
object[] native = uiTestControl.NativeElement as object[];
IAccessible acc = native[0] as IAccessible;
return acc.accName;
}
throw new NotSupportedException();
}
public override int GetControlSupportLevel(UITestControl uiTestControl)
{
if (string.Equals(uiTestControl.TechnologyName, "MSAA",
StringComparison.OrdinalIgnoreCase) &&
uiTestControl.ControlType.NameEquals("Custom"))
{
return (int)ControlSupport.ControlSpecificSupport;
}
// This is not my control, so return NoSupport
return (int)ControlSupport.NoSupport;
}
public override string[] GetPredefinedSearchProperties(Type specializedClass)
{
return null;
}
public override string GetPropertyForAction(UITestControl uiTestControl, UITestAction action)
{
return null;
}
public override string[] GetPropertyForControlState(UITestControl uiTestControl, ControlStates uiState, out bool[] stateValues)
{
stateValues = null;
return null;
}
public override Type GetPropertyNamesClassType(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
return typeof(AccessibleControl.PropertyNames);
return null;
}
public override Type GetSpecializedClass(UITestControl uiTestControl)
{
if (uiTestControl.ControlType.NameEquals("Custom"))
return typeof(AccessibleControl);
return null;
}
public override void SetPropertyValue(UITestControl uiTestControl, string propertyName, object value)
{
return;
}
}
}
ExtensionPackage:
[assembly: Microsoft.VisualStudio.TestTools.UITest.Extension.UITestExtensionPackage(
"AccessibleNameExtensionPackage",
typeof(CUITExtension.AccessibleNameExtensionPackage))]
namespace CUITExtension
{
class AccessibleNameExtensionPackage : UITestExtensionPackage
{
public override string PackageDescription
{
get { return "Supports coded UI testing by using the AccessibleName"; }
}
public override string PackageName
{
get { return "AccessibleName Extension Package"; }
}
public override string PackageVendor
{
get { return "Microsoft (sample)"; }
}
public override Version PackageVersion
{
get { return new Version(1, 0); }
}
public override Version VSVersion
{
get { return new Version(14, 0); }
}
public override void Dispose() { }
public override object GetService(Type serviceType)
{
if (serviceType == typeof(UITestPropertyProvider))
{
if (propertyProvider == null)
{
propertyProvider = new AccessibleNamePropertyProvider();
}
return propertyProvider;
}
return null;
}
private UITestPropertyProvider propertyProvider = null;
}
}
WinControl:
namespace CUITExtension
{
public class AccessibleControl : WinControl
{
public AccessibleControl(UITestControl c) : base(c)
{
TechnologyName = "MSAA";
SearchProperties.Add(UITestControl.PropertyNames.ControlType, "Custom");
}
public virtual string AccessibleName
{
get
{
return (string)GetProperty("AccessibleName");
}
}
}
}
Now the Coded UI Test Builder is showing the AccessibleName and is also generating AccessibleName as a SearchProperty.
UIMap:
public AccessibleControl UIItemCustom
{
get
{
if ((this.mUIItemCustom == null))
{
this.mUIItemCustom = new AccessibleControl(this);
#region Search Criteria
this.mUIItemCustom.SearchProperties["AccessibleName"] = "UniqueName1";
this.mUIItemCustom.SearchProperties[WinControl.PropertyNames.ClassName] = "TEdit";
this.mUIItemCustom.WindowTitles.Add("Title");
#endregion
}
return this.mUIItemCustom;
}
}
*I have changed the Searchproperties here (only for the post, i didnt changed the generated code)
Now when I start the test, I get an exception that says that AccessibleName is not an valid searchproperty. I got this exception before, when i havent implemented the extension yet. But I thougth by implementing the propertyprovider AccessibleName should be a valid searchproperty now.
I tried to debug it, but it seems like by searching the Control it doesnt use the propertyprovider and i have no idea why?
I hope you can help me and if you need more information just ask.
Paul
I got the problem with the valid searchproperty to work.
I overrode the GetValidSearchProperties method from WinControl.
protected override Dictionary<string, bool> GetValidSearchProperties()
{
Dictionary<string, bool> searchProperties = base.GetValidSearchProperties();
if (!searchProperties.ContainsKey("AccessibleName"))
searchProperties.Add("AccessibleName", true);
return searchProperties;
}
I'm using Prism with IoC. The problem is to pass an object (like collections) through navigation. I was watching this post: How to Pass an object when navigating to a new view in PRISM 4
And this is the solution
I extract the hash code of the object and save it in a Dictionary, with the hash code as the key and the object as the value of the pair.
Then, I attach the hash code to the UriQuery.
After, I only have to get the hash code that comes from the Uri on the target view and use it to request the original object from the Dictionary.
Some example code:
Parameter repository class:
public class Parameters
{
private static Dictionary<int, object> paramList =
new Dictionary<int, object>();
public static void save(int hash, object value)
{
if (!paramList.ContainsKey(hash))
paramList.Add(hash, value);
}
public static object request(int hash)
{
return ((KeyValuePair<int, object>)paramList.
Where(x => x.Key == hash).FirstOrDefault()).Value;
}
}
The caller code:
UriQuery q = null;
Customer customer = new Customer();
q = new UriQuery();
Parameters.save(customer.GetHashCode(), customer);
q.Add("hash", customer.GetHashCode().ToString());
Uri viewUri = new Uri("MyView" + q.ToString(), UriKind.Relative);
regionManager.RequestNavigate(region, viewUri);
The target view code:
public partial class MyView : UserControl, INavigationAware
{
// some hidden code
public void OnNavigatedTo(NavigationContext navigationContext)
{
int hash = int.Parse(navigationContext.Parameters["hash"]);
Customer cust = (Customer)Parameters.request(hash);
}
}
That's it.
I'm not sure if this solution is the best to pass objects. I guess this maybe would be a service. Is a good way to do this or is there a better way to do it?
I posted an easier way. Mentioning it here for reference -
I would use the OnNavigatedTo and OnNavigatedFrom methods to pass on the objects using the NavigationContext.
First derive the viewmodel from INavigationAware interface -
public class MyViewModel : INavigationAware
{ ...
You can then implement OnNavigatedFrom and set the object you want to pass as navigation context as follows -
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
SharedData data = new SharedData();
...
navigationContext.NavigationService.Region.Context = data;
}
and when you want to receive the data, add the following piece of code in the second view model -
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.NavigationService.Region.Context != null)
{
if (navigationContext.NavigationService.Region.Context is SharedData)
{
SharedData data = (SharedData)navigationContext.NavigationService.Region.Context;
...
}
}
}
I just started using Prism and this is one of the first limitations I ran into. I solved it a different way. I first created a class that inherits from Uri and implements IDictionary (plus some generic methods for easier access)
public class NavigationUri : Uri, IDictionary<Type, object>
{
private IDictionary<Type, object> _internalDictionary = new Dictionary<Type, object>();
public NavigationUri(string uriString) : base(uriString, UriKind.Relative)
{
}
public NavigationUri(string uriString, UriKind uriKind) : base(uriString, uriKind)
{
}
public void Add<T>(T value)
{
Add(typeof(T), value);
}
public void Add(Type key, object value)
{
_internalDictionary.Add(key, value);
}
public bool ContainsKey<T>()
{
return ContainsKey(typeof (T));
}
public bool ContainsKey(Type key)
{
return _internalDictionary.ContainsKey(key);
}
public ICollection<Type> Keys
{
get { return _internalDictionary.Keys; }
}
public bool Remove<T>()
{
return Remove(typeof (T));
}
public bool Remove(Type key)
{
return _internalDictionary.Remove(key);
}
public bool TryGetValue<T>(out object value)
{
return TryGetValue(typeof (T), out value);
}
public bool TryGetValue(Type key, out object value)
{
return _internalDictionary.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return _internalDictionary.Values; }
}
public object this[Type key]
{
get { return _internalDictionary[key]; }
set { _internalDictionary[key] = value; }
}
public void Add(KeyValuePair<Type, object> item)
{
_internalDictionary.Add(item);
}
public void Clear()
{
_internalDictionary.Clear();
}
public bool Contains(KeyValuePair<Type, object> item)
{
return _internalDictionary.Contains(item);
}
public void CopyTo(KeyValuePair<Type, object>[] array, int arrayIndex)
{
_internalDictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _internalDictionary.Count; }
}
public bool IsReadOnly
{
get { return _internalDictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<Type, object> item)
{
return _internalDictionary.Remove(item);
}
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
return _internalDictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _internalDictionary.GetEnumerator();
}
}
Then I created a class that inherits from RegionNavigationContentLoader. On GetContractFromNavigationContext I store the passed in Uri so I can access it in the CreateNewRegionItem method. In that method I check to see if the Uri is the NavigationUri and if so I loop though adding all the dependency injection overrides. I'm using Unity but I assume the code could easy be converted to another IOC container.
public class BaseRegionNavigationContentLoader : RegionNavigationContentLoader
{
private Uri _uri;
private IServiceLocator _serviceLocator;
private IUnityContainer _unityContainer;
public BaseRegionNavigationContentLoader(IServiceLocator serviceLocator, IUnityContainer unityContainer) : base(serviceLocator)
{
_serviceLocator = serviceLocator;
_unityContainer = unityContainer;
}
protected override string GetContractFromNavigationContext(NavigationContext navigationContext)
{
_uri = navigationContext.Uri;
return base.GetContractFromNavigationContext(navigationContext);
}
protected override object CreateNewRegionItem(string candidateTargetContract)
{
object instance;
try
{
var uri = _uri as NavigationUri;
if (uri == null)
{
instance = _serviceLocator.GetInstance<object>(candidateTargetContract);
}
else
{
// Create injection overrides for all the types in the uri
var depoverride = new DependencyOverrides();
foreach (var supplant in uri)
{
depoverride.Add(supplant.Key, supplant.Value);
}
instance = _unityContainer.Resolve<object>(candidateTargetContract, depoverride);
}
}
catch (ActivationException exception)
{
throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture, "CannotCreateNavigationTarget", new object[] { candidateTargetContract }), exception);
}
return instance;
}
}
Now in the prism Bootstrapper you need to register the BaseRegionNavigationContentLoader as IRegionNavigationContentLoader in the ConfigureServiceLocator method. Make sure you mark it as TransientLifetimeManager so it gets newed up each time. The default registration for IRegionNavigationContentLoader is container controlled which makes it act like a singleton but we need a new one each time since we need to pass the uri from one method to the next in a property.
Now I can wright code like the following and still use constructor injection.
var uri = new NavigationUri("MessageBoxView");
uri.Add(messageBoxEventArgs);
regionManager.RequestNavigate(RegionNames.MainRegion, uri);
Adding smart tags in VS2010 (or VS2008 for that matter) to my control makes VS crash.
The following designer is used for the action list:
internal class DataEditorDesigner : ComponentDesigner {
[...]
public override DesignerActionListCollection ActionLists {
get {
var lists = new DesignerActionListCollection();
lists.AddRange(base.ActionLists);
lists.Add(new DataEditorActionList(Component));
return lists;
}
}
}
internal class DataEditorActionList : DesignerActionList {
public DataEditorActionList(IComponent component) : base(component) {}
public override DesignerActionItemCollection GetSortedActionItems() {
var items = new DesignerActionItemCollection();
items.Add(new DesignerActionPropertyItem("DataSource", "Data Source:", "Data"));
items.Add(new DesignerActionMethodItem(this, "AddControl", "Add column..."));
return items;
}
private void AddControl() {
System.Windows.Forms.MessageBox.Show("dpa");
}
}
The DataSource property is declared as such:
[AttributeProvider(typeof (IListSource))]
[DefaultValue(null)]
public object DataSource {
[...]
Any ideas on how to debug it?
I've found the solution. It's necessary to add wrapper properties to DesignerActionList classes, that's where they're read from, not from the actual component. Also, it's necessary to write such code:
internal static PropertyDescriptor GetPropertyDescriptor(IComponent component, string propertyName) {
return TypeDescriptor.GetProperties(component)[propertyName];
}
internal static IDesignerHost GetDesignerHost(IComponent component) {
return (IDesignerHost) component.Site.GetService(typeof (IDesignerHost));
}
internal static IComponentChangeService GetChangeService(IComponent component) {
return (IComponentChangeService) component.Site.GetService(typeof (IComponentChangeService));
}
internal static void SetValue(IComponent component, string propertyName, object value) {
PropertyDescriptor propertyDescriptor = GetPropertyDescriptor(component, propertyName);
IComponentChangeService svc = GetChangeService(component);
IDesignerHost host = GetDesignerHost(component);
DesignerTransaction txn = host.CreateTransaction();
try {
svc.OnComponentChanging(component, propertyDescriptor);
propertyDescriptor.SetValue(component, value);
svc.OnComponentChanged(component, propertyDescriptor, null, null);
txn.Commit();
txn = null;
} finally {
if (txn != null)
txn.Cancel();
}
}
and then use it:
[AttributeProvider(typeof (IListSource))]
public object DataSource {
get { return Editor.DataSource; }
set { DesignerUtil.SetValue(Component, "DataSource", value); }
}
and so on for other properties.