Reflection of object properties - c#

i have this code
public class ParameterOrderInFunction : Attribute
{
public int ParameterOrder { get; set; }
public ParameterOrderInFunction(int parameterOrder)
{
this.ParameterOrder = parameterOrder;
}
}
public interface IGetKeyParameters
{
}
public class Person: IGetKeyParameters
{
[ParameterOrderInFunction(4)]
public string Age { get; set; }
public string Name { get; set; }
[ParameterOrderInFunction(3)]
public string Address { get; set; }
[ParameterOrderInFunction(2)]
public string Language { get; set; }
[ParameterOrderInFunction(1)]
public string City { get; set; }
public string Country { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Address = "my address";
person.Age = "32";
person.City = "my city";
person.Country = "my country";
Test t = new Test();
string result = t.GetParameter(person);
//string result = person.GetParameter();
Console.ReadKey();
}
}
public class Test
{
public string GetParameter(IGetKeyParameters obj)
{
string[] objectProperties = obj.GetType()
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ParameterOrderInFunction)))
.Select(p => new
{
Attribute = (ParameterOrderInFunction)Attribute.GetCustomAttribute(p, typeof(ParameterOrderInFunction), true),
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
})
.OrderBy(p => p.Attribute.ParameterOrder)
.Select(p => p.PropertyValue)
.ToArray();
string keyParameters = string.Join(string.Empty, objectProperties);
return keyParameters;
}
}
What i am trying to do is to get properties values as one string with some order .
it work fine if i put the function GetParameter inside the Person class.
however, i want to use the function GetParameter with other class as well,
so i create empty interface.
Now i want that every object that is of type IGetKeyParameters can use the function.
but i am getting exception in the line:
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()

You should change loading properties from this (that doesn't have such properties) to parameter object:
PropertyValue = p.GetValue(obj) == null ? string.Empty : p.GetValue(obj).ToString()

You are passing the wrong reference as parameter to the method, you need to pass the object which you used to get the type and properties, so change:
p.GetValue(this) // this means pass current instance of containing class i.e. Test
to:
p.GetValue(obj)
Your statement p.GetValue(this) currenly means to pass the current instance of class Test as parameter which is i am pretty sure not what you want.
in your example code.

Related

How to get the properties with name, old value and new value after comparing two class objects

I have two objects with the same properties. What I need is the list of properties having a difference in values. Consider the following classes (there can be many properties but they will always be the same in both the classes). In the example, I have two objects with a difference in lastname and address. So, I need a list that contains the property name along with the old and new values.
public class A
{
public string ColumnName { get; set; }
public string oldValue { get; set; }
public string newValue { get; set; }
}
public class B
{
public string ColumnName { get; set; }
public string oldValue { get; set; }
public string newValue { get; set; }
}
public class C
{
public string ColumnName { get; set; }
public string oldValue { get; set; }
public string newValue { get; set; }
}
A objA = new A();
objA.firstName = "XYZ";
objA.lastName = "ABC";
objA.address = "123";
B objB = new B();
objB.firstName = "XYZ";
objB.lastName = "ABCD";
objB.address = "456";
List<C> listObj = new List<C>();
//Here I need a list of the columns having different value. For example this should be the result after comparison.
listObj.Add(new C{ColumnName = "lastName", oldValue = "ABC", newValue = "ABCD"});
listObj.Add(new C{ColumnName = "address", oldValue = "123", newValue = "456"});
Is there a way this can be achieved (preferably LINQ but not necessary)?
EDIT: For those looking for a solution, this is exactly what I was looking for: CompareNETObjects
The result of this LINQ is list of C class instances with different property name, old object value and new object value.
typeof(A).GetProperties().OrderBy(p => p.Name)
.Zip(typeof(B).GetProperties().OrderBy(p => p.Name),
((aInfo, bInfo) => !aInfo.GetValue(objA).Equals(bInfo.GetValue(objB))
? new C()
{
ColumnName = aInfo.Name,
oldValue = aInfo.GetValue(objA).ToString(),
newValue = bInfo.GetValue(objB).ToString()
}
: null))
.Where(x => !(x is null)).ToList();
More generall with anonymous class instead of C.
typeof(A).GetProperties().OrderBy(p => p.Name)
.Zip(typeof(B).GetProperties().OrderBy(p => p.Name),
((aInfo, bInfo) => !aInfo.GetValue(objA).Equals(bInfo.GetValue(objB))
? new
{
ColumnName = aInfo.Name,
oldValue = aInfo.GetValue(objA),
newValue = bInfo.GetValue(objB)
}
: null))
.Where(x => !(x is null)).ToList();

Compare 2 Properties of equals object

I have a class with 2 properties
public class SampleClass
{
public string Name { get; set; }
public List<Component> Components { get; set; }
}
And another class which is hold some string properties.
public class Component
{
public string Name { get; set; }
public string Age{ get; set; }
}
I have instance of this class created and added into a List
SampleClass classWithValues = new SampleClass();
var listComponent = new List<Component>();
listComponent.add(new Component{Name = "Random string",Age = "31"})
classWithValues.Components = listComponent;
classWithValues.Name = "TestName"
var listWithObjectClass = new List<SampleClass>();
listWithObjectClass.add(classWithValues);
Then i made a new instance of the SampleClass class and add exactly the same value into the properties :
SampleClass classWithValues1 = new SampleClass();
var listComponent1 = new List<Component>();
listComponent1.add(new Component{Name = "Random string",Age = "31"})
classWithValues1.Components = listComponent1;
classWithValues1.Name = "TestName";
And here is coming the strange part :
if I compare the property Names inside the list with the second instance of the Sample class with the new instance of the same class:
bool alreadyExists = listWithObjectClass.Any(x => x.Name == classWithValues1 .Name);
the result is true BUT
if I compare the List properties
bool alreadyExists = listWithObjectClass.Any(x => x.Components == classWithValues1.Components);
the result is false ?!
Can someone please give some information about this behavior.
Sorry my first answer was not quite right...
In order to get alreadyExist to be true you need to put in place property comparison in your classes as otherwise the equality comparison performed is the default reference comparison. Your objects contains the same property values but are actually different instances... The default equality comparison for objects is comparing references not content.
Try this...
void Main()
{
SampleClass classWithValues = new SampleClass();
var listComponent = new Components();
listComponent.Add(new Component{Name = "Random string",Age = "31"});
classWithValues.Components = listComponent;
classWithValues.Name = "TestName";
var listWithObjectClass = new List<SampleClass>();
listWithObjectClass.Add(classWithValues);
SampleClass classWithValues1 = new SampleClass();
var listComponent1 = new Components();
listComponent1.Add(new Component{Name = "Random string",Age = "31"});
classWithValues1.Components = listComponent1;
classWithValues1.Name = "TestName";
bool alreadyExists = listWithObjectClass.Any(x => x.Components.Equals(classWithValues1.Components));
}
public class SampleClass
{
public string Name { get; set; }
public Components Components { get; set; }
}
public class Component : IEquatable<Component>
{
public string Name { get; set; }
public string Age{ get; set; }
public bool Equals(Component otherComponent)
{
return Name == otherComponent.Name && Age == otherComponent.Age;
}
}
public class Components :List<Component>, IEquatable<Components>
{
public bool Equals(Components otherComponents)
{
if(this.Count!= otherComponents.Count) return false;
return this.TrueForAll(a=> otherComponents.Any(q=>q.Equals(a)))
&& otherComponents.TrueForAll(a=> this.Any(q=>q.Equals(a)));
}
}
The first comparison is about comparing the value of the two string. However, the second comparison is about Comparing two different object which their reference are different. Indeed, for the second comparison, compare their hashCode. To watch this, you can call .GetHashCode() for these two objects.
listComponent.GetHashCode() == listComponent1.GetHashCode() // false
listComponent[0].GetHashCode() == listComponent1[0].GetHashCode() // false

cannot convert type decimal to double

I have a simple web service calling a sql view table through entity framework. I can bring all the columns in string fine but not the column in numeric like UID_NUM(numeric(38,8), null) in SQL. I have AddressALL class to set columns like below and error out at p.UID_NUM in LINQ.
public class GISAddressWebService : System.Web.Services.WebService
{
[WebMethod]
public AddressALL[] getAddress()
{
try
{
List<view_COBADDRESS> address = new List<view_COBADDRESS>();
using (GISAddressEntities database = new GISAddressEntities())
{
return database.view_COBADDRESS
.Where(p => p.UNIT_NUM == "103")
.Select(p => new AddressALL { UID_NUM = p.UID_NUM, ADD_FULL = p.ADD_FULL, POSTALCITY = p.POSTALCITY, ZIP5 = p.ZIP5}).ToArray();
}
}
catch (Exception)
{
return null;
}
}
}
public class AddressALL
{
public double UID_NUM { get; set; }
public string TLID { get; set; }
public string ADD_FULL { get; set; }
public string POSTALCITY { get; set; }
public string STATE { get; set; }
public string ZIP5 { get; set; }
public string IN_OUT { get; set; }
}
The decimal has more significant figures than the double, therefore it can be more precise and it also takes up slightly more memory. Because of this difference fou must explicitly program this change of type through (double)p.UID_NUM.
return database.view_COBADDRESS
.Where(p => p.UNIT_NUM == "103")
.Select(p => new AddressALL { UID_NUM = System.Convert.ToDouble(p.UID_NUM), ADD_FULL = p.ADD_FULL, POSTALCITY = p.POSTALCITY, ZIP5 = p.ZIP5}).ToArray();
MSDN
The obvious solution, instead of
.Select(p => new AddressALL
{
UID_NUM = p.UID_NUM,
ADD_FULL = p.ADD_FULL,
POSTALCITY = p.POSTALCITY,
ZIP5 = p.ZIP5
});
write
.Select(p => new AddressALL
{
UID_NUM = Convert.ToDouble(p.UID_NUM),
ADD_FULL = p.ADD_FULL,
POSTALCITY = p.POSTALCITY,
ZIP5 = p.ZIP5
});
In your select statement .Select(p => new AddressALL{ ... }) you are doing a projection that is trying to pick a new object of type AddressALL for each p, and you are using the object initializer syntax {...} to match the properties of your source objects p with the properties of your target type AddressALL.
Your error message however suggests your p.UID_NUM is of type decimal, while the UID_NUM property on your AddressALL is of type double. Therefore you have to convert the values to the necessary target type.

Is it possible to create conditional attribute as DisplayIf?

I want to create an attribute to use with my viewmodel. I want to display different textstrings depending on a third value.
I would like to do something like this...
[DisplayIf("IsPropertyValid", true, Name="value 1")]
[DisplayIf("IsPropertyValid", false, Name="value 2")]
public string MyProperty { get; set; }
public bool IsPropertyValid { get; set; }
Depending on whether my value IsPropertyValid is true or not I want to show one or the other. Ie. When property IspPropertyValid equals true "value 1" will be the displaytext and if not it will be "value 2".
Is this possible with ASPNET.MVC attributes? Or even better... a combinated one like....
[DisplayIf("IsPropertyValid", new {"value 1", "value 2"})].
public string MyProperty { get; set; }
public bool IsPropertyValid { get; set; }
Then the attribute checks the value of IsPropertyValid and makes sure the value displayed is "value 1" or "value 2".
Here's an example of how to go about this.
What we'll do is create a simple class called Person and display some basic information about them.
A Person has two properties
Name
IsActive
The IsActive property is a bool value and will be the property used to determine what the user's name is displayed as.
Ultimately what we'll do is apply a new attribute called DisplayIf to the Name property. It looks like this:
[DisplayIf("IsActive", "This value is true.", "This value is false.")]
First, let's create our model. Create a class called Person and put it into a Models folder.
Models/Person.cs
public class Person
{
[DisplayIf("IsActive", "This value is true.", "This value is false.")]
public string Name { get; set; }
public bool IsActive { get; set; }
}
Create a folder called Attributes and then put the following class in it:
Attributes/DisplayIfAttribute.cs
public class DisplayIfAttribute : Attribute
{
private string _propertyName;
private string _trueValue;
private string _falseValue;
public string PropertyName
{
get { return _propertyName; }
}
public string TrueValue
{
get { return _trueValue; }
}
public string FalseValue
{
get { return _falseValue; }
}
public DisplayIfAttribute(string propertyName, string trueValue, string falseValue)
{
_propertyName = propertyName;
_trueValue = trueValue;
_falseValue = falseValue;
}
}
Let's create a simple controller and action. We'll use the common /Home/Index.
Controllers/HomeController.cs
public class HomeController : Controller
{
public ActionResult Index()
{
HomeIndexViewModel viewModel = new HomeIndexViewModel();
Person male = new Person() { Name = "Bob Smith", IsActive = true };
Person female = new Person() { Name = "Generic Jane", IsActive = false };
Person[] persons = {male, female};
viewModel.Persons = persons;
return View(viewModel);
}
}
Create a new folder called ViewModels and create a HomeViewModels.cs class.
ViewModels/HomeViewModels.cs
public class HomeIndexViewModel
{
public IEnumerable<Person> Persons { get; set; }
}
Our Index view is very simple.
Views/Home/Index.cshtml
#model HomeIndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div>
#Html.DisplayForModel()
</div>
DisplayForModel will work when you create this display template:
Views/Home/DisplayTemplates/HomeIndexViewModel.cshtml
#model HomeIndexViewModel
#Html.DisplayFor(m => m.Persons)
DisplayFor -> Persons will work when you create this display template:
Views/Shared/DisplayTemplates/Person.cshtml
#model Person
#foreach (var prop in ViewData.ModelMetadata.Properties)
{
if (prop.HasDisplayIfAttribute())
{
<p>#Html.DisplayIfFor(x => prop)</p>
}
else
{
<p>#Html.DisplayFor(x => prop.Model)</p>
}
}
But what are these methods in this display template? Create a new folder called Extensions and add the following classes:
Extensions/ModelMetaDataExtensions.cs
public static class ModelMetaDataExtensions
{
public static bool HasDisplayIfAttribute(this ModelMetadata data)
{
var containerType = data.ContainerType;
var containerProperties = containerType.GetProperties();
var thisProperty = containerProperties.SingleOrDefault(x => x.Name == data.PropertyName);
var propertyAttributes = thisProperty.GetCustomAttributes(false);
var displayIfAttribute = propertyAttributes.FirstOrDefault(x => x is DisplayIfAttribute);
return displayIfAttribute != null;
}
}
Extensions/HtmlHelperExtensions.cs
public static class HtmlHelperExtensions
{
public static IHtmlString DisplayIfFor<TModel, TProperty>
(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
where TProperty : ModelMetadata
{
string returnValue = string.Empty;
var modelMetaData = expression.Compile().Invoke(helper.ViewData.Model);
var containerType = typeof(TModel);
var containerProperties = containerType.GetProperties();
var propertyInfo = containerProperties
.SingleOrDefault(x => x.Name == modelMetaData.PropertyName);
var attribute = propertyInfo.GetCustomAttributes(false)
.SingleOrDefault(x => x is DisplayIfAttribute) as DisplayIfAttribute;
var conditionalTarget = attribute.PropertyName;
var conditionalTargetValue = (bool)containerType
.GetProperty(conditionalTarget).GetValue(helper.ViewData.Model);
if (conditionalTargetValue)
{
returnValue = attribute.TrueValue;
}
else
{
returnValue = attribute.FalseValue;
}
return MvcHtmlString.Create(returnValue);
}
}
The final output:

Unable to cast a list

I am getting a error in the line below.
temp.day1_veh_p = string.Join(Environment.NewLine, day1.Where(x => x.plannedTriips == 1).Select(x => new {value=x.vehicleNumber+":"+x.shiftCompletedOn }).Cast<string>().ToArray());
Th error Message being
Unable to cast object of type '<>f__AnonymousType0`1[System.String]' to type 'System.String'.
The list day1 is of type
public class tripDetails
{
public string accountID { get; set; }
public string supplierName { get; set; }
public string supplierCode { get; set; }
public DateTime shiftFrom { get; set; }
public DateTime shiftTo { get; set; }
public int plannedTriips { get; set; }
public int actualTrips { get; set; }
public DateTime forDate { get; set; }
public string vehicleNumber { get; set; }
public string shiftCompletedOn { get; set; }
public class Comparer : IEqualityComparer<tripDetails>
{
public bool Equals(tripDetails x, tripDetails y)
{
return x.supplierCode == y.supplierCode;
}
public int GetHashCode(tripDetails obj)
{
return (obj.supplierCode).GetHashCode();
}
}
}
What exactly Am i doing wrong??
The problem is the new { value = ... }
Replace:
Select(x => new {value=x.vehicleNumber+":"+x.shiftCompletedOn }).Cast<string>()
with
Select(x => x.vehicleNumber+":"+x.shiftCompletedOn)
and you're sorted. You won't need the Cast<string>() at all.
Your original code creates, for each record, a new instance of an anonymous type that has a member called value with the string desired; the second version just creates the string.
In a way, it is no different to trying this:
class Foo
{
public string Bar {get;set;}
}
...
var foo = new Foo { Bar = "abc" };
string s = (string)foo; // doesn't compile
Yes, an anonymous type is not a string, so replace this
.Select(x => new { value = x.vehicleNumber + ":" + x.shiftCompletedOn })
with
.Select(x => x.vehicleNumber + ":" + x.shiftCompletedOn)
Then you can use the query(you don't need to create a new array) for string.Join.
It's also helpful to use multiple lines, it makes your code much more readable:
var vehicles = day1.Where(x => x.plannedTriips == 1)
.Select(x => x.vehicleNumber + ":" + x.shiftCompletedOn);
string str = string.Join(Environment.NewLine, vehicles);
Replace this
(x => new {value=x.vehicleNumber+":"+x.shiftCompletedOn }).Cast<string>()
by this
(x => String.Format("{0}\:{1}", x.vehicleNumber, x.shiftCompletedOn))
When you are doing new { ... } you are creating items of anonymous type and then (Cast<string()) trying explicitly cast to string and such conversion is not defined - youn ends up with appropriate exception.

Categories

Resources