I have a cached control object form a UserControl in asp.net cache.
I like to access my control and make a copy of it to freely change it without having any affect on the base cached control.
Hi
I found the code that I was looking for.
I put it here maybe it helps you too.
/// <summary>
/// this method makes a copy of object as a clone function
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
public static object Clone_Control(object o)
{
Type type = o.GetType();
PropertyInfo[] properties = type.GetProperties();
Object retObject = type.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, o, null);
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, null), null);
}
}
return retObject;
}
It is certainly possible to do what you are asking to do. And it is very straightforward. Something like this maybe?
private static void ChangeMyControlTextFromCache(string controlName, string newText, Panel containingPanel)
{
MyControl original = ViewState[controlName] as MyControl;
if (original == null)
{
original = new MyControl();
ViewState[controlName] = original;
}
MyControl clone = CloneMyControl(original);
clone.Text = newText;
if (containingPanel.Children.Contains(original))
containingPanel.Children.Remove(original);
containingPanel.Children.Add(clone);
}
private static MyControl CloneMyControl(MyControl original)
{
MyControl clone = new MyControl();
clone.Text = original.Text;
clone.SomeOtherProperty = original.SomeOtherProperty;
return clone;
}
Here is another clone method that will also work with MONO.
It clones the passed control and it's children.
Note: A cloned control cannot be added to a different page (or in a later post-back) even it has not been added to any page yet. It might seem to work on Windows but will crash on MONO so I guess it is generally not meant to be done.
public static Control CloneControl(Control c) {
var clone = Activator.CreateInstance(c.GetType()) as Control;
if (c is HtmlControl) {
clone.ID = c.ID;
foreach (string key in ((HtmlControl)c).Attributes.Keys)
((HtmlControl)clone).Attributes.Add(key, (string)((HtmlControl)c).Attributes[key]);
}
else {
foreach (PropertyInfo p in c.GetType().GetProperties()) {
// "InnerHtml/Text" are generated on the fly, so skip them. "Page" can be ignored, because it will be set when control is added to a Page.
if (p.CanRead && p.CanWrite && p.Name != "InnerHtml" && p.Name != "InnerText" && p.Name != "Page") {
try {
p.SetValue(clone, p.GetValue(c, p.GetIndexParameters()), p.GetIndexParameters());
}
catch {
}
}
}
}
for (int i = 0; i < c.Controls.Count; ++i)
clone.Controls.Add(CloneControl(c.Controls[i]));
return clone;
}
try this for page includes LiteralControl and ResourceBasedLiteralControl.
public static System.Web.UI.Control CloneControl(Control c)
{
Type type = c.GetType();
var clone = (0 == type.GetConstructors().Length) ? new Control() : Activator.CreateInstance(type) as Control;
if (c is HtmlControl)
{
clone.ID = c.ID;
AttributeCollection attributeCollection = ((HtmlControl)c).Attributes;
System.Collections.ICollection keys = attributeCollection.Keys;
foreach (string key in keys)
{
((HtmlControl)c).Attributes.Add(key, (string)attributeCollection[key]);
}
}else if(c is System.Web.UI.LiteralControl){
clone = new System.Web.UI.LiteralControl(((System.Web.UI.LiteralControl)(c)).Text);
}
else
{
PropertyInfo[] properties = c.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
// "InnerHtml/Text" are generated on the fly, so skip them. "Page" can be ignored, because it will be set when control is added to a Page.
if (p.Name != "InnerHtml" && p.Name != "InnerText" && p.Name != "Page" && p.CanRead && p.CanWrite)
{
try
{
ParameterInfo[] indexParameters = p.GetIndexParameters();
p.SetValue(clone, p.GetValue(c, indexParameters), indexParameters);
}
catch
{
}
}
}
}
int cControlsCount = c.Controls.Count;
for (int i = 0; i < cControlsCount; ++i)
{
clone.Controls.Add(CloneControl(c.Controls[i]));
}
return clone;
}
ref:
Dynamic Web Controls, Postbacks, and View State
By Scott Mitchell
Upvoted answers didn't work for me. Here's my solution to creating a copy of an ASP.NET control object.
// Copies the custom properties on the control. To use it, cast your object to it.
public static object CopyUserControl(object obj, object obj2)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
Type type2 = obj2.GetType();
PropertyInfo[] properties2 = type.GetProperties();
foreach (PropertyInfo property in properties)
{
foreach (PropertyInfo property2 in properties2)
{
// Match the loops and ensure it's not a non-custom property
if (property.Name == property2.Name && property.SetMethod != null && !property.SetMethod.IsSecuritySafeCritical)
property2.SetValue(obj2, property.GetValue(obj, null));
}
}
return obj2;
}
It's useful when you have to copy an ASCX control on the same page with a different html id. Usage:
// Copying myAscxControl1 onto myAscxControl2
myAscxControl2 = (ObjectNameToBeCasted)CopyUserControl(myAscxControl1, myAscxControl2);
It should work with different typed objects too. Property names that you want to copy must match though.
Related
I working hard on a JSON-Deserializer on my own. Just for training. I am nearly finished, but I have a copying issue.
I already have this:
public void CopyValues<T>(T target, T source)
{
Type t = typeof(T);
var properties = t.GetProperties().Where(prop => prop.CanRead && prop.CanWrite);
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
if (value != null)
prop.SetValue(target, value, null);
}
}
The Main Problem is here. I have a Property containing 2 Properties. Like content.link and content.value.
If I use the Copy Function it copys right. No discussion. But if I put the copy function into a loop, and the data is filled up, the source having also
"content", but without link and value.
If I copy again, the already correctly filled properties are getting overridden, and the result is that I have only null at conent.link and content.value.
Is there a way, to check if link and value is set to null ?
In order to copy the nested properties, you will need use a recursive function:
public static void DeepCopy<T>(T target, T source)
{
DeepCloneImpl(typeof(T), source, target);
}
public static T DeepClone<T>(T template)
where T : new()
{
return (T)DeepCloneImpl(typeof(T), template);
}
private static object DeepCloneImpl(Type type, object template, object stump = null)
{
if (template == null)
{
return null;
}
var clone = stump ?? Activator.CreateInstance(type);
var clonableProperties = type.GetProperties()
.Where(x => x.GetMethod != null && x.SetMethod != null);
foreach (var property in clonableProperties)
{
var propertyType = property.PropertyType;
if (propertyType.GetTypeInfo().IsValueType || propertyType == typeof(string))
{
var value = property.GetValue(template);
property.SetValue(clone, value);
}
else if (propertyType.GetTypeInfo().IsClass && propertyType.GetConstructor(Type.EmptyTypes) != null)
{
var value = DeepCloneImpl(propertyType, property.GetValue(template));
property.SetValue(clone, value);
}
else if (propertyType.IsArray)
{
var source = property.GetValue(template) as Array;
if (source == null)
{
continue;
}
var elementType = propertyType.GetElementType();
if (elementType.GetTypeInfo().IsValueType || elementType == typeof(string))
{
var copies = Array.CreateInstance(elementType, source.Length);
Array.Copy(source, copies, source.Length);
property.SetValue(clone, copies);
}
else if (elementType.GetTypeInfo().IsClass)
{
var copies = Array.CreateInstance(elementType, source.Length);
for (int i = 0; i < source.Length; i++)
{
var copy = DeepCloneImpl(elementType, source.GetValue(i));
copies.SetValue(copy, i);
}
property.SetValue(clone, copies);
}
}
}
return clone;
}
This should cover most of the use case, however you will have to handle self/circular references.
From what I can gather, the problem is values are being overridden. And from looking at your method, it appears to be doing what you'd expect, it's a soft clone of property values. Depending on what a correctly filled property is in your situation, you'll have to make a comparison that prevents the property from being set again. Perhaps value != null should actually be value == null so that you're only setting the value when there wasn't already one. Because of the generic typing of this method you don't have to worry about property mismatches if that's why you're using != null. Both arguments, source and target, will be of the same type.
Although many questions have been posted, none seem to help me on my issue.
I've started a new Generics / Reflection adventure and I'm just trying to get my head around the syntax and concepts.
I have a Generic class with X amount of properties and one being a collection, all is working fine but I'm having problems extracting the values from the collection props by property name.
foreach (var property in typeof(T).GetProperties())
{
if (property.Name == "Props")
{
foreach (var item in (IEnumerable)property.GetValue(type, null))
{
var propertyName = "";
var newValue = "";
var oldValue = "";
sbDescription.AppendLine(strDescriptionVals
.Replace("{0}", (item.ToString() == "PropertyName") ? item.ToString() : "" + ", "));
sbAllNotes.AppendLine(strAllNotes
.Replace("{0}", (item.ToString() == "PropertyName") ? item.ToString() : "")
.Replace("{1}", (item.ToString() == "NewValue") ? item.ToString() : "")
.Replace("{2}", (item.ToString() == "OldValue") ? item.ToString() : ""));
}
}
}
As you can see I've pinpointed the property Props and now I want to loop through that and pull values by property name.
item.ToString() just prints the namespace of the class property and not the value
Hoping you kind folk can point me in the right direction?
You probably want to recursively "dive" into the items properties.
Note, only conceptual code fragments, no guarantee that it works as I write it here:
void Print(object value, string propertyName)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsPrimitive)
{
sbAllNotes.AppnedLine(.. propertyName .. value ..);
}
else if ((typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
{
PrintCollectioType((IEnumerable)value);
}
else
{
PrintComplexType(value);
}
}
void PrintCollectioType(IEnumerable collection)
{
foreach (var item in collection)
{
Print(item, "Collection Item");
}
}
void PrintComplexType(IEnumerable collection)
{
foreach(var property in owner.GetType().GetProperties())
{
var propertyName = property.Name;
var propertyValue = property.GetValue(owner);
Print(item, propertyName);
}
}
To print the "Props", do:
var props = typeof(T).GetProperty("Props");
var propsValue = props.GetValue(rootObject);
Print(propsValue, "Root");
After playing with the code and understanding the logic a little more, it occurred to me that is was as simple as "Getting the type and Property" on the Props iteration:
//Get the collection property
foreach (var property in typeof(T).GetProperties())
{
if (property.Name == "Props")
{
foreach (var item in (IEnumerable)property.GetValue(type, null))
{
//Because props is a collection, Getting the type and property on each pass was essential
var propertyName = item.GetType().GetProperty("PropertyName").GetValue(item, null);
var newValue = item.GetType().GetProperty("NewValue").GetValue(item, null);
var oldValue = item.GetType().GetProperty("OldValue").GetValue(item, null);
sbDescription.AppendLine(strDescriptionVals
.Replace("{0}", (propertyName != null) ? propertyName.ToString() : "" + ", "));
sbAllNotes.AppendLine(strAllNotes
.Replace("{0}", (propertyName != null) ? propertyName.ToString() : "")
.Replace("{1}", (newValue != null) ? newValue.ToString() : "")
.Replace("{2}", (oldValue != null) ? oldValue.ToString() : ""));
}
}
}
I have a 'Invoice' WinForm C# where it contains the usual textboxes and an unbound datagridview.
I used a 'Invoice' class object to store the form fields' values. The datagridview rows are stored to a List < SubInvoice> properties in the 'Invoice'.
public static bool CompareObjects(object original, object altered)
{
Type o = original.GetType();
Type a = altered.GetType();
foreach (PropertyInfo p in o.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine("Original: {0} = {1}", p.Name, p.GetValue(original, null));
Console.WriteLine("Altered: {0} = {1}", p.Name, p.GetValue(altered, null));
if (p.GetValue(original, null).ToString() != p.GetValue(altered, null).ToString())
{
//Console.WriteLine("Not Equal");
return false;
}
}
return true;
}
I am using a global class with a method that uses Reflection to loop through the original 'invoice' object and compared it with the altered 'invoice'. If there is any difference, the user will be notified to save the invoice.
The above method works fine with all the properties of 'Invoice' class but I do not know how to check for the List in which it stores the value of each datagridrow into a 'SubInvoice' class object that stored it as an object in the list.
Can someone please give me some help here? I have also checked for similar thread in stackoverflow and other forums but in vain.
Update: I need to create a global generic method that will check for all type of classes. It can be 'Invoice', 'Customer'. The purpose is to track any changes made to the form at any particular instance and prompt the user to save.
thank you all for your help.
I have stumbled into the following article while still eager to create a global generic method through reflection.
http://www.c-sharpcorner.com/UploadFile/1a81c5/custom-extension-method-to-compare-list-in-C-Sharp/
Here's the code if anyone is interested.
public static bool CompareObjects(object original, object altered)
{
bool result = true;
//Get the class
Type o = original.GetType();
Type a = altered.GetType();
//Cycle through the properties.
foreach (PropertyInfo p in o.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine("Original: {0} = {1}", p.Name, p.GetValue(original, null));
Console.WriteLine("Altered: {0} = {1}", p.Name, p.GetValue(altered, null));
if (!p.PropertyType.IsGenericType)
{
if (p.GetValue(original, null) != null && p.GetValue(altered, null) != null)
{
if (!p.GetValue(original, null).ToString().Equals(p.GetValue(altered, null).ToString()))
{
result = false;
break;
}
}
else
{
//If one is null, the other is not
if ((p.GetValue(original, null) == null && p.GetValue(altered, null) != null) || (p.GetValue(original, null) != null && p.GetValue(altered, null) == null))
{
result = false;
break;
}
}
}
}
return result;
}
public static bool CompareLists<T>(this List<T> original, List<T> altered)
{
bool result = true;
if (original.Count != altered.Count)
{
return false;
}
else
{
if (original != null && altered != null)
{
foreach (T item in original)
{
T object1 = item;
T object2 = altered.ElementAt(original.IndexOf(item));
Type objectType = typeof(T);
if ((object1 == null && object2 != null) || (object1 != null && object2 == null))
{
result = false;
break;
}
if (!CompareObjects(object1, object2))
{
result = false;
break;
}
}
}
else
{
if ((original == null && altered != null) || (original != null && altered == null))
{
return false;
}
}
}
return result;
}
Usage: if (!xx.CompareObjects(searchedInvoice, alteredInvoice) | !xx.CompareLists(searchedInvoice.SubInvoice, alteredInvoice.SubInvoice))
If you only whant to inform the user to save the pending changes then you should implement INotifyPropertyChanged instead of doing some black magic.
One trick I've seen before is to mark the objects as serializable, then serialize them in to strings and compare the strings. It may not be the most efficient method, but it is quick, easy, and works for almost any kind of data class. It even works for "complex" nested classes that have references to other classes.
One down-side is that it won't easily tell you what specifically changed, just that something is different.
I have written a method that loops through all the properties of an object and maps them to controls that have the same name (or prefix + name). The issue is that I have certain controls inside an update panel (drop down lists that change when a different option is selected) that are not being found when running through this method. I read this and adapted the method below to accommodate for that, but it still will not find the controls inside the update panel. All the controls have IDs and runat="server".
public static void MapObjectToPage(this object obj, Control parent, string prefix = "")
{
Type type = obj.GetType();
Dictionary<string, PropertyInfo> props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToDictionary(info => prefix + info.Name.ToLower());
ControlCollection theControls = parent is UpdatePanel ? ((UpdatePanel)parent).ContentTemplateContainer.Controls : parent.Controls;
foreach (Control c in theControls)
{
if (props.Keys.Contains(c.ClientID.ToLower()) && props[c.ClientID.ToLower()].GetValue(obj, null) != null)
{
string key = c.ClientID.ToLower();
if (c.GetType() == typeof(TextBox))
{
((TextBox)c).Text = props[key].PropertyType == typeof(DateTime?)
|| props[key].PropertyType == typeof(DateTime)
? ((DateTime)props[key].GetValue(obj, null)).ToShortDateString()
: props[key].GetValue(obj, null).ToString();
}
else if (c.GetType() == typeof(HtmlInputText))
{
((HtmlInputText)c).Value = props[key].PropertyType == typeof(DateTime?)
|| props[key].PropertyType == typeof(DateTime)
? ((DateTime)props[key].GetValue(obj, null)).ToShortDateString()
: props[key].GetValue(obj, null).ToString();
}
//snip!
}
if (c is UpdatePanel
? ((UpdatePanel)c).ContentTemplateContainer.HasControls()
: c.HasControls())
{
obj.MapObjectToPage(c);
}
}
}
Try to add this two methods to a new or existing class:
public static List<Control> FlattenChildren(this Control control)
{
var children = control.Controls.Cast<Control>();
return children.SelectMany(c => FlattenChildren(c).Where(a => a is Label || a is Literal || a is Button || a is ImageButton || a is GridView || a is HyperLink || a is TabContainer || a is DropDownList || a is Panel)).Concat(children).ToList();
}
public static List<Control> GetAllControls(Control control)
{
var children = control.Controls.Cast<Control>();
return children.SelectMany(c => FlattenChildren(c)).Concat(children).ToList();
}
You can call the GetAllControls method with the updatePanel as parameter (or the main container). The method returns all the children of the 'control' parameter. Also, you can remove the Where clause to retrieve all the controls (not those of a certain type).
I hope this will help you!
UpdatePanel is directly access. You don't need to/can't use FindControl to find it. Use this
foreach (Control ctrl in YourUpdatepanelID.ContentTemplateContainer.Controls)
{
if (ctrl.GetType() == typeof(TextBox))
((TextBox)(ctrl)).Text = string.Empty;
if (ctrl.GetType() == typeof(CheckBox))
((CheckBox)(ctrl)).Checked = false;
}
I've almost got it, but I am having a hard time with doing the last part gracefully. This answer was update based on the answer submitted by Jeremy Thompson. This is what I have so far:
public void SetupTree()
{
var types = Assembly.Load("Data").GetTypes().Where(t => t.IsPublic && t.IsClass);
if (types.Count() > 0)
{
if (treeView_left.Nodes.Count == 0)
{
treeView_left.Nodes.Add(new TreeNode("Structure Data"));
treeView_left.Nodes[0].Nodes.Add(types.First().GetHashCode().ToString(), types.First().Name);
}
foreach (Type type in types)
{
BuildTree(types, type, treeView_left.Nodes[0].Nodes[0]);
}
}
treeView_left.Refresh();
}
private void BuildTree(IEnumerable<Type> types, Type type, TreeNode parentNode)
{
var tempNodes = treeView_left.Nodes.Find(type.BaseType.GetHashCode().ToString(), true);
if (tempNodes.Count() > 0)
{
parentNode = tempNodes[0];
if (tempNodes.Count() != 1)
{
//TODO: warning
}
}
if (parentNode != null)
{
if (treeView_left.Nodes.Find(type.GetHashCode().ToString(), true).Count() == 0)
{
parentNode.Nodes.Add(type.GetHashCode().ToString(), type.Name);
}
foreach (Type t in types.Where(x => x.IsSubclassOf(type)))
{
BuildTree(types, t, parentNode.Nodes[type.GetHashCode().ToString()]);
}
}
}
This produces the result I am looking for, but I suspect I am doing some of this the hardway. If anyone could point out a cleaner method for the last part I would appriciate it.
I haven't tested this yet, but notice the Recursive call LoadAllChildren calls itself.
public void SetupTree()
{
Assembly dataLib = Assembly.Load("Data");
TreeNode theTree = new TreeNode("Assembly Data");
foreach (Type type in dataLib.GetTypes())
{
LoadAllChildren(dataLib, type, theTree);
}
treeView_left.Nodes.Add(theTree); //Optimisation - bind all nodes in one go rather than adding individually
}
private void LoadAllChildren(Assembly dataLib,Type type, TreeNode parentNode)
{
if (type.IsPublic && type.IsClass)
{
TreeNode node = new TreeNode(type.Name);
parentNode.Nodes.Add(node);
var types = dataLib.GetTypes().Where(x => x.IsSubclassOf(type));
foreach (Type t in types)
{
LoadAllChildren(dataLib, t, node);
}
}
}
I hope this is enough to get you over the hurdle, feel free to ask Q's
I wont respond quickly as my PC is about to get rebuilt:(