Neater way to modify values in ModelBinder - c#

Currently, I have a modelbinder like this. This does not work for nested classes, lists and dictionaries. Is there a way to bind model from form and then change values nicely.
public class AuditModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext modelBindingContext)
{
char[] delimeter = { '|' };
if (modelBindingContext == null)
{
throw new ArgumentNullException(nameof(modelBindingContext));
}
// var modelName = modelBindingContext.ModelName; // returns empty string
// ValueProviderResult valueResult = modelBindingContext.ValueProvider.GetValue(modelName); // doesn't work
var enumerable = (IEnumerable)modelBindingContext.ValueProvider;
var formValueProvider = enumerable.OfType<FormValueProvider>().Single();
var metadata = modelBindingContext.ModelMetadata;
var type = metadata.UnderlyingOrModelType;
var fields = typeof(FormValueProvider).GetField(
"_values",
BindingFlags.NonPublic | BindingFlags.Instance
);
var formCollection = fields.GetValue(formValueProvider).ToJson();
var listOfData = JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>(formCollection);
var dictionary = new Dictionary<string, object>();
foreach (var data in listOfData)
{
var key = data.Key;
var value = GetValue(modelBindingContext, data.Key, (string)((JToken)data.Value).FirstOrDefault());
dictionary[key] = value;
}
var model = JsonConvert.DeserializeObject(dictionary.ToJson(), type);
if (modelBindingContext.ModelType.GetInterfaces().Contains(typeof(IControl)))
{
var qc = model as IControl;
if (qc != null)
{
var request = modelBindingContext.HttpContext.Request;
string control = request.Form["control"];
if (!string.IsNullOrEmpty(control))
{
string[] components = control.Split(delimeter);
qc.Field1 = Convert.ToInt32(components[0]);
qc.Field2 = components[1];
qc.Fields.Add(components[2]);
}
}
}
modelBindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}

Related

How to extract particular properties from generic list based on property name sent as string array?

I have a Generic List of type List<InstanceDataLog> which has huge number of properties in it. I want to pass names of a few properties to a method and want to extract a refined List from within this list.
public void Export(string col) //a,b,c
{
string [] selectedcol = col.Split(',');
var grid = new GridView();
var data = TempData["InstanceDataList"];
List<InstanceDataLog> lst = new List<InstanceDataLog>();
List<EToolsViewer.APIModels.InstanceDataLog> lstrefined = new List<InstanceDataLog>();
lst= (List<EToolsViewer.APIModels.InstanceDataLog>)TempData["InstanceDataList"];
var r= lst.Select(e => new {e.a, e.b}).ToList();// I would like to replace these hardcoded properties with names of properties present in `selectedcol `
grid.DataSource =r;
grid.DataBind();
}
To clear things up further, suppose InstanceDataLog has 5 properties : a,b,c,d,e I would like pass a,b and be able to extract a new list with only properties a,b
EDIT:
$('#export').mousedown(function () {
window.location = '#Url.Action("Export", "TrendsData",new { col = "a,b,c" })';
});
You could use such method to get properties:
private object getProperty(EToolsViewer.APIModels.InstanceDataLog e, string propName)
{
var propInfo =typeof(EToolsViewer.APIModels.InstanceDataLog).GetProperty(propName);
return propInfo.GetValue(e);
}
and with another function you could get all properties you want:
private dynamic getProperties(string[] props, EToolsViewer.APIModels.InstanceDataLog e )
{
var ret = new ExpandoObject() as IDictionary<string, Object>;;
foreach (var p in props)
{
ret.Add(p, getProperty(e, p));
}
return ret;
}
The problem occurs if you try to assign DataSource with expando object. Solution is described hier:
Binding a GridView to a Dynamic or ExpandoObject object
We do need one more method:
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
var data = items.ToArray();
if (data.Count() == 0) return null;
var dt = new DataTable();
foreach (var key in ((IDictionary<string, object>)data[0]).Keys)
{
dt.Columns.Add(key);
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}
and use it:
var r = lst.Select(e => getProperties(selectedcol, e)).ToList();
grid.DataSource = ToDataTable(r);
The same thing, but ready to run for LinqPad:
void Main()
{
var samples = new[] { new Sample { A = "A", B = "B", C = "C" }, new Sample { A = "A1", B = "B2", C = "C1" } };
var r = samples.Select(e => getProperties(new[] {"A", "C", "B"}, e)).ToList();
r.Dump();
}
private object getProperty(Sample e, string propName)
{
var propInfo = typeof(Sample).GetProperty(propName);
return propInfo.GetValue(e);
}
private dynamic getProperties(string[] props, Sample e)
{
var ret = new ExpandoObject() as IDictionary<string, Object>; ;
foreach (var p in props)
{
ret.Add(p, getProperty(e, p));
}
return ret;
}
public class Sample
{
public string A { get; set;}
public string B { get; set;}
public string C { get; set;}
}
With output:
To keep compiler type/name checking suggest to pass a Func<InstanceDataLog, TResult> instead of array of names
public void Export<TResult>(Func<InstanceDataLog, TResult> selectProperties)
{
var grid = new GridView();
var data = TempData["InstanceDataList"];
var originalList = (List<InstanceDataLog>)TempData["InstanceDataList"];
var filteredList = originalList.Select(selectProperties).ToList();
grid.DataSource = filteredList;
grid.DataBind();
}
Then use it:
Export(data => new { Id = data.Id, Name = data.Name });

How do I get the value of a field with generic typeof

Given that I have a parameter TEntity which will be an class, how do i get the value of one of its fields.
public class ProductStrategy<TContract> : IContractCreatorStrategy<TContract>
{
public IContract<TContract> CreateContract<TEntity>(TEntity dbEntity)
{
var productEntity = Convert.ChangeType(dbEntity, typeof(TEntity));
var contractType = typeof (TContract);
var entityType = dbEntity.GetType();
var contract = Activator.CreateInstance(contractType);
var contractFields = contractType.GetProperties().ToList();
var entityFields = entityType.GetProperties().ToList();
foreach (var contractField in contractFields)
{
foreach (var entityField in entityFields)
{
if (entityField.Name.Contains(contractField.Name))
{
//get the value of the entityfield and set the contract field value to the it.
}
}
}
return new ProductContract<TContract>();
return productContract;
}
}
var fieldValue = entityField.GetValue(dbEntity, null);
contractField.SetValue(contract, fieldValue);

Port of BeanUtils to .NET

I have a system using dynamic run-time configuration of dynamically loaded types.
The system is loading Types based on XML and creating instances. It then reads "properties" from the XML file, setting these properties on the created instance. At this time, it works on simple properties directly on the instance, however, these types can have a hierarchy of settings unknown to the calling part.
I'm looking for a utils-library similar to Java BeanUtils [http://commons.apache.org/proper/commons-beanutils/]. I would want it to be able to do something like this (pseudo code):
Util.SetProperty(someInstance, "property1.property2, someValue);
Or perhaps with extensions:
someInstance.SetProperty("property1.property2, someValue);
And the reverse with Get of course.
Note that BeanUtils has it's own style of describing properties so it works for most types of properties, including lists.
Anything, or perhaps suggestions on different approach to the problem?
Here is a helper class which supports Lists, Dictionaries and nested Properties, you should extend it if you need to support multiple indexers (very rare cases).
public static class Helper
{
public static void SetProperty(object instance, string propery, object value)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var properties = propery.Split('.');
var type = instance.GetType();
object[] index = null;
PropertyInfo property = null;
for (var i = 0; i < properties.Length; i++)
{
var indexValue = Regex.Match(properties[i], #"(?<=\[)(.*?)(?=\])").Value;
if (string.IsNullOrEmpty(indexValue))
{
property = type.GetProperty(properties[i], flags);
index = null;
}
else
{
property =
type.GetProperty(properties[i].Replace(string.Format("[{0}]", indexValue), string.Empty),
flags);
index = GetIndex(indexValue, property);
}
if (i < properties.Length - 1)
instance = property.GetValue(instance, index);
type = instance.GetType();
}
property.SetValue(instance, value, index);
}
public static object GetProperty(object instance, string propery)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var properties = propery.Split('.');
var type = instance.GetType();
foreach (var p in properties)
{
var indexValue = Regex.Match(p, #"(?<=\[)(.*?)(?=\])").Value;
object[] index;
PropertyInfo property;
if (string.IsNullOrEmpty(indexValue))
{
property = type.GetProperty(p, flags);
index = null;
}
else
{
property =
type.GetProperty(p.Replace(string.Format("[{0}]", indexValue), string.Empty),
flags);
index = GetIndex(indexValue, property);
}
instance = property.GetValue(instance, index);
type = instance.GetType();
}
return instance;
}
private static object[] GetIndex(string indicesValue, PropertyInfo property)
{
var parameters = indicesValue.Split(',');
var parameterTypes = property.GetIndexParameters();
var index = new object[parameterTypes.Length];
for (var i = 0; i < parameterTypes.Length; i++)
index[i] = parameterTypes[i].ParameterType.IsEnum
? Enum.Parse(parameterTypes[i].ParameterType, parameters[i])
: Convert.ChangeType(parameters[i], parameterTypes[i].ParameterType);
return index;
}
}
and here is some Examples:
public enum Qwerty
{
Q,W,E,R,T,Y
}
class A
{
private int[,] _array=new int[10,10];
public B PropertyB { get; set; }
public int this[int i, int j]
{
get { return _array[i, j]; }
set { _array[i, j] = value; }
}
}
class B
{
public int Value { get; set; }
}
Nested Properties:
var a = new A { PropertyB = new B() };
Helper.SetProperty(a, "PropertyB.Value", 100);
var value = Helper.GetProperty(a, "PropertyB.Value");
Indexer (with multiple index):
var a = new A { PropertyB = new B() };
Helper.SetProperty(a, "Item[1,1]", 100);
var value = Helper.GetProperty(a, "Item[1,1]");
Lists:
var list = new List<int>() { 0, 1, 2, 3, 4 };
Helper.SetProperty(list, "Item[2]", 200);
var value = Helper.GetProperty(list, "Item[2]");
Nested Properties with Lists:
var list = new List<A>() { new A { PropertyB = new B() } };
Helper.SetProperty(list, "Item[0].PropertyB.Value", 75);
var value = Helper.GetProperty(list, "Item[0].PropertyB.Value");
Dictionaries:
var dic = new Dictionary<int, A> { { 100, new A { PropertyB = new B() } } };
var newA = new A { PropertyB = new B() { Value = 45 } };
Helper.SetProperty(dic, "Item[100]", newA);
var value = Helper.GetProperty(dic, "Item[100].PropertyB.Value");
Dictionaries with Enumeration as Key:
var dic = new Dictionary<Qwerty, A> { { Qwerty.Q, new A { PropertyB = new B() } } };
var newA = new A { PropertyB = new B() { Value = 45 } };
Helper.SetProperty(dic, "Item[Q]", newA);
var value = Helper.GetProperty(dic, "Item[Q].PropertyB.Value");
Enumeration as Value:
var list = new List<Qwerty>() { Qwerty.Q, Qwerty.W, Qwerty.E, Qwerty.R, Qwerty.T, Qwerty.Y };
Helper.SetProperty(list, "Item[2]", Qwerty.Q);
var value = Helper.GetProperty(list, "Item[2]");

Logging entities change using SessionInterceptor in orchard

I wanted to log changes of certain entities (marked with attribute) so, I created AbstractSessionInterceptor's descendant to get access to entity changes. Also I want to know who did this changes, so I need to access current user, so through IWorkContextAccessor I'm creating IWorkContextScope, getting WorkContext and trying to get user, when existing entity is being edited I'm able to access current user, when new entity is created with contentmanager I'm getting timeout exception.. Then I getting WorkContext via IWorkContextAccessor.GetContext() I'm get infinite loop (interceptor is being called again and again). Any Ideas and suggestion would be appreciated.
Thanks.
Source:
public class AccountInterceptor : AbstractSessionInterceptor
{
private IProtocolLogger _logger;
private readonly Type _mainAttrType = typeof(ProtocolAttribute);
private readonly Type _fieldAttrType = typeof(ProtocolFieldAttribute);
private readonly IWorkContextAccessor _contextAccessor;
ISessionFactoryHolder _sessionFactoryHolder;
public AccountInterceptor(IWorkContextAccessor contextAccessor, ISessionFactoryHolder sessionFactoryHolder)
{
_contextAccessor = contextAccessor;
_sessionFactoryHolder = sessionFactoryHolder;
}
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
{
var t = entity.GetType();
var attributes = t.GetCustomAttributes(_mainAttrType, true);
if (attributes.Length != 0)
{
IWorkContextScope scope = _contextAccessor.CreateWorkContextScope();
WorkContext context = scope.WorkContext;
if (context != null)
{
var attr = (ProtocolAttribute)attributes.FirstOrDefault();
var currentDic = currentState.Select((s, i) => new { S = s, Index = i }).ToDictionary(x => x.Index, x => x.S);
var prvDic = previousState.Select((s, i) => new { S = s, Index = i }).ToDictionary(x => x.Index, x => x.S);
var diff = compare(currentDic, prvDic);
if (!attr.LogAllData)
{
List<string> properties = new List<string>();
foreach (var propety in t.GetProperties())
{
var propertyAttributes = propety.GetCustomAttributes(_fieldAttrType, true);
if (propertyAttributes.Length != 0)
properties.Add(propety.Name);
}
if (properties.Count != 0)
{
var necesseryProps = propertyNames.Select((s, i) => new { S = s, Index = i }).Where(p => properties.Contains(p.S)).ToDictionary(x => x.Index, x => x.S);
TupleList<int, object, object> ToRemove = new TupleList<int, object, object>();
foreach (var tuple in diff)
{
if (!necesseryProps.Keys.Contains(tuple.Item1))
{
ToRemove.Add(tuple);
}
}
ToRemove.ForEach(d => diff.Remove(d));
}
}
if (diff.Count != 0)
{
_logger = ProtocolLogger.GetInstance();
var sessionFactory = _sessionFactoryHolder.GetSessionFactory();
var session = sessionFactory.OpenSession();
var user = GetCurrentUser(session, context.HttpContext);
string propertiesFormat = GetPropertiesStringFormat(diff, propertyNames);
object[] param = new object[] { DateTime.Now, entity, propertyNames };
string entityId = string.Empty;
try
{
if (entity is IAuditable)
{
entityId = ((IAuditable)entity).Id.ToString();
}
}
catch (Exception)
{
entityId = entity.ToString();
}
foreach (var pair in diff)
{
ProtocolPropertyInfo info = new ProtocolPropertyInfo(propertyNames[pair.Item1], Convert.ToString(pair.Item2), Convert.ToString(pair.Item3));
_logger.Log(user, entity, entityId, session, context, Operation.Write, info);
}
session.Flush();
session.Close();
}
}
}
return base.OnFlushDirty(entity, id, currentState, previousState, propertyNames, types);
}
private object GetCurrentUser(ISession session, HttpContextBase httpContext)
{
if (httpContext == null || !httpContext.Request.IsAuthenticated || !(httpContext.User.Identity is FormsIdentity))
{
return null;
}
var formsIdentity = (FormsIdentity)httpContext.User.Identity;
var userData = formsIdentity.Ticket.UserData ?? "";
// the cookie user data is {userId};{tenant}
var userDataSegments = userData.Split(';');
if (userDataSegments.Length != 2)
{
return null;
}
var userDataId = userDataSegments[0];
var userDataTenant = userDataSegments[1];
int userId;
if (!int.TryParse(userDataId, out userId))
{
return null;
}
Type regType = Assembly.Load("Orchard.Users").GetTypes().First(t => t.Name == "UserPartRecord");
var user = session.Get(regType, userId);
return user;
}
private string GetPropertiesStringFormat(TupleList<int, object, object> diffDic, string[] propertyNames)
{
StringBuilder result = new StringBuilder();
foreach (var pair in diffDic)
{
result.AppendFormat("Property name {0}, New value {1}, Old value {2}", propertyNames[pair.Item1], pair.Item2, pair.Item3);
}
return result.ToString();
}
private TupleList<int, object, object> compare(Dictionary<int, object> dic1, Dictionary<int, object> dic2)
{
var diff = new TupleList<int, object, object>();
foreach (KeyValuePair<int, object> pair in dic1)
{
if (!Equals(pair.Value, dic2[pair.Key]))
{
diff.Add(pair.Key, pair.Value, dic2[pair.Key]);
}
}
return diff;
}
}
Never ever start new work context inside your interceptor - infinite loop is guaranteed. And in fact, you don't have to. Each interceptor is already instantiated per work context, so you can inject dependencies via ctor, as usual.
To access current user you can either:
inject IOrchardServices and use .WorkContext.CurrentUser property, or
or use contextAccessor.GetContext() to get context and then call CurrentUser on it.
Also, be careful with performing database operations from inside the interceptor as those will most likely result in infinite loops and throw stack overflow exceptions.

How to get property name and value from generic model with generic list?

Using the following model as an example.
public class FooModel
{
public FooModel()
{
Bars= new List<BarModel>();
}
[ManyToMany]
public IList<BarModel> Bars{ get; set; }
}
public class BarModel
{
public int Id { get; set; }
}
I need to extrapolate the List<BarModel> from a fooModel object, and build up a Dictionary<string, object> from each BarModel in the list.
Let's say I create the following object.
var fooModel = new FooModel();
var bar1 = new BarModel {Id = 1};
var bar2 = new BarModel {Id = 2};
fooModel.Bars = new List<BarModel>{bar1,bar2};
And now I want to get all properties within Foo that have the [ManyToMany] attribute.
// First I call the method and pass in the model
DoSomething(fooModel);
// Next I extract some values (used elsewhere)
public DoSomething<TModel>(IModel model){
var dbProvider = ...;
var mapper = new AutoMapper<TModel>();
var tableName = GetTableName( typeof( TModel ) );
UpdateJoins( dbProvider, fooModel, tableName, mapper );
}
// Finally I begin dealing with the collection.
private static void UpdateJoins<TModel> ( IDbProvider dbProvider, TModel model, string tableName, IAutoMapper<TModel> mapper ) where TModel : class, new()
{
foreach (
var collection in
model.GetType()
.GetProperties()
.Where( property => property.GetCustomAttributes( typeof( ManyToManyAttribute ), true ).Any() ) )
{
if ( !IsGenericList( collection.PropertyType ) )
throw new Exception( "The property must be a List" );
// Stuck Here - pseudo code
//====================
foreach (loop the collection)
var collectionName = ...; // Bar
var nestedPropertyName = ...; // Id
var rightKey = collectionName + nestedPropertyName; // BarId
var nestedPropertyValue = ...; // 1
}
}
In the example above, the OUTER foreach is only going to run ONCE because there is only one Property within FooModel that is decorated with the [ManyToMany] attribute.
Therefore PropertyInfo property is a List<BarModel>
How do I do the above INNER foreach and extract the required data?
This may get you on the right track. The idea is if you encounter a [ManyToMany] / generic list you reflect it using recursive call to the same method and then flatten the returned values to form a unique key. You probably will need to tweak it to suit your problem. The below code returns a dictionary with formatted key strings built from collection names, indexes and property names. E.G:
Bars[0].Id = 1
Bars[1].Id = 2
Code:
//This is just a generic wrapper for the other Reflect method
private static Dictionary<string, string> Reflect<TModel>(TModel Model)
{
return Reflect(Model.GetType(), Model);
}
private static Dictionary<string, string> Reflect(Type Type, object Object)
{
var result = new Dictionary<string, string>();
var properties = Type.GetProperties();
foreach (var property in properties)
{
if (
property.GetCustomAttributes(typeof(ManyToManyAttribute), true).Any() &&
property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
{
var genericType = property.PropertyType.GetGenericArguments().FirstOrDefault();
var listValue = (IEnumerable)property.GetValue(Object, null);
int i = 0;
foreach (var value in listValue)
{
var childResult = Reflect(genericType, value);
foreach (var kvp in childResult)
{
var collectionName = property.Name;
var index = i;
var childPropertyName = kvp.Key;
var childPropertyValue = kvp.Value;
var flattened = string.Format("{0}[{1}].{2}", collectionName, i, childPropertyName);
result.Add(flattened, childPropertyValue);
}
i++;
}
}
else
{
result.Add(property.Name, property.GetValue(Object, null).ToString());
}
}
return result;
}

Categories

Resources