As part of a test I am trying to compare similar objects using reflection.
The objects may or may not have multiple levels of nested params.
For example:
public class Connection{
public string Ip{get; set}
public string Id{get; set}
public string Key{get; set}
public string Transport{get; set}
public Parameters ParametersObj = new Parameters();
public class Parameters
{
public string AssignedName { get; set; }
public string CategoryType { get; set; }
public string Status { get; set; }
}
};
This is just an example of a class, I need this method to deal with any type of object without knowing the number of depth level.
I am doing something like this after I have made sure the two objects are of the same type.
bool result = true;
foreach (var objParam in firstObj.GetType().GetProperties())
{
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null || !value1.Equals(value2))
{
logger.Error("Property: " + objParam.Name);
logger.Error("Values: " + value1?.ToString() + " and " + value2?.ToString());
result = false;
}
}
return result;
It works perfectly for the first level of params but it ignores completely any nested objects. In this example I would like it to compare the values inside the parameters object and if they are different the log to print error "Property: Parameters.Status".
I would recommend to look into some tool which already does that (do not know one which does exactly that but FluentAssertions for example can handle object graph comparisons). But in the nutshell you can check if type is primitive or overrides Equals and call your method recursively. Something like the following:
bool Compare(object firstObj, object secondObj)
{
if (object.ReferenceEquals(firstObj, secondObj))
{
return true;
}
var type = firstObj.GetType();
var propertyInfos = firstObj.GetType().GetProperties();
foreach (var objParam in propertyInfos)
{
var methodInfo = objParam.PropertyType.GetMethod(nameof(object.Equals), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, new []{typeof(object)});
var overridesEquals = methodInfo?.GetBaseDefinition().DeclaringType == typeof(object);
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null)
{
// Log
return false;
}
if (object.ReferenceEquals(value1, value2))
{
continue;
}
if (type.IsPrimitive || overridesEquals)
{
if (value1.Equals(value2))
{
continue;
}
// Log?
return false;
}
if (!Compare(value1, value2))
{
// log ?
return false;
}
}
return true;
}
P.S.
Note that Connection.ParametersObj is not a property it is field so it will be ignored by both yours and mine implementations
Consider using source generators instead of reflection.
This does not handle collections.
The problem is that you only do one loop, without looking at the objects within each object. Here's a quick recursive function I threw together. (untested!)
// You can make it non-generic, this just ensures that both arguments are the same type.
static void Go<T>(T left, T right, Action<object, object, PropertyInfo> onFound, int depth = 2)
{
if (left is null)
return;
foreach (var p in left.GetType().GetProperties())
{
var l = p.GetValue(left);
var r = p.GetValue(right);
if (l is null || r is null || !l.Equals(r))
onFound(l, r, p);
if (depth is not 0)
Go(l, r, onFound, depth - 1);
}
}
Usage:
var arg1 = new Connection()
{
ParametersObj = new() { AssignedName = "foo" }
};
var arg2 = new Connection()
{
ParametersObj = new() { AssignedName = "bar" }
};
Go(arg1, arg2, Log); // goes 2 layers deep is you don't specify the last parameter
void Log(object l, object r, PropertyInfo p)
{
logger.Error($"Property: {p.Name}");
logger.Error($"Values: {l} and {r}");
}
I need help to solve a problem, my problem is as follows, I have the following object
public class Teste
{
public string Descricao { get; set; }
public Time Time { get; set; }
}
.
public class Time
{
public string Nome { get; set; }
public Time (string nome)
{
Nome = nome;
}
}
I would like to be able to obtain the complete path of a certain property.
var teste = new Teste();
teste.Descricao = "bar";
teste.Time = new Time("foo");
var b = GetProperties(teste, "Nome");
//expected return: "Time.Nome"
I was testing something I arrived at the following method
public static IEnumerable<Tuple<string, string>> GetProperties(object obj, string propertyPath)
{
var objType = obj.GetType();
if (objType.IsValueType || objType.Equals(typeof(string)))
return Enumerable.Repeat(Tuple.Create(propertyPath, obj.ToString()), 1);
else
{
if (obj == null)
return Enumerable.Repeat(Tuple.Create(propertyPath, string.Empty), 1);
else
{
return from prop in objType.GetProperties()
where prop.CanRead && !prop.GetIndexParameters().Any()
let propValue = prop.GetValue(obj, null)
let propType = prop.PropertyType
from nameValPair in GetProperties(propValue, string.Format("{0}.{1}", propertyPath, prop.Name))
select nameValPair;
}
}
}
but it returns everything to me and I would like it to return a specific property.
I think there are some issues with searching properties that come from system modules. You have to decide which properties are worth recursively descending and which ones are not. Also, you'll have to maintain a list of objects that you have already visited to ensure that you do not follow cycles. I think a breadth-first search would be best, but for this example, I'll code a depth-first search. Also, I just return the first match, not all matches, you can adjust as needed. Furthermore, it returns a (mostly useless) string version of the path rather than a list of reflected properties that would be needed to actually access it (You'd have to do reflection again to locate the properties by name to retrieve the value from this "path" string.)
I'll start you off with a basic implementation. Likely someone else can improve upon it.
static string GetPropertyPath(object obj, string name, List<object> visited = null)
{
// does the object have the property?
Type t = obj.GetType();
var properties = t.GetProperties();
foreach (var property in properties) {
if (property.Name == name) {
// that's it!
return name;
}
}
// if we get here, it's because we didn't find the property.
if (visited == null) {
visited = new List<object>();
visited.Add(obj);
}
// Get all the properties of the first object and keep searching,
// keeping track of objects we've visited already.
foreach (var property in properties) {
// Limit which kinds of properties we search
if (object.ReferenceEquals(typeof(Program).Module, property.Module)) {
// get the value of the property
object obj2 = property.GetValue(obj);
// Do not search any previously visited objects
if (!visited.Any(o => object.ReferenceEquals(o, obj2))) {
visited.Add(obj2);
string path = GetPropertyPath(obj2, name, visited);
if (path != null) {
// found it!
return property.Name + "." + path;
}
}
}
}
return null;
}
Example
static void Main(string[] args)
{
var teste = new Teste();
teste.Descricao = "bar";
teste.Time = new Time("foo");
var b = GetPropertyPath(teste, "Nome"); // "Time.Nome"
}
I have an ASP.NET Web API (version 4) REST service where I need to pass an array of integers.
Here is my action method:
public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}
And this is the URL that I have tried:
/Categories?categoryids=1,2,3,4
You just need to add [FromUri] before parameter, looks like:
GetCategories([FromUri] int[] categoryIds)
And send request:
/Categories?categoryids=1&categoryids=2&categoryids=3
As Filip W points out, you might have to resort to a custom model binder like this (modified to bind to actual type of param):
public IEnumerable<Category> GetCategories([ModelBinder(typeof(CommaDelimitedArrayModelBinder))]long[] categoryIds)
{
// do your thing
}
public class CommaDelimitedArrayModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var key = bindingContext.ModelName;
var val = bindingContext.ValueProvider.GetValue(key);
if (val != null)
{
var s = val.AttemptedValue;
if (s != null)
{
var elementType = bindingContext.ModelType.GetElementType();
var converter = TypeDescriptor.GetConverter(elementType);
var values = Array.ConvertAll(s.Split(new[] { ","},StringSplitOptions.RemoveEmptyEntries),
x => { return converter.ConvertFromString(x != null ? x.Trim() : x); });
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
}
else
{
// change this line to null if you prefer nulls to empty arrays
bindingContext.Model = Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0);
}
return true;
}
return false;
}
}
And then you can say:
/Categories?categoryids=1,2,3,4 and ASP.NET Web API will correctly bind your categoryIds array.
I recently came across this requirement myself, and I decided to implement an ActionFilter to handle this.
public class ArrayInputAttribute : ActionFilterAttribute
{
private readonly string _parameterName;
public ArrayInputAttribute(string parameterName)
{
_parameterName = parameterName;
Separator = ',';
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsKey(_parameterName))
{
string parameters = string.Empty;
if (actionContext.ControllerContext.RouteData.Values.ContainsKey(_parameterName))
parameters = (string) actionContext.ControllerContext.RouteData.Values[_parameterName];
else if (actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName] != null)
parameters = actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName];
actionContext.ActionArguments[_parameterName] = parameters.Split(Separator).Select(int.Parse).ToArray();
}
}
public char Separator { get; set; }
}
I am applying it like so (note that I used 'id', not 'ids', as that is how it is specified in my route):
[ArrayInput("id", Separator = ';')]
public IEnumerable<Measure> Get(int[] id)
{
return id.Select(i => GetData(i));
}
And the public url would be:
/api/Data/1;2;3;4
You may have to refactor this to meet your specific needs.
In case someone would need - to achieve same or similar thing(like delete) via POST instead of FromUri, use FromBody and on client side(JS/jQuery) format param as $.param({ '': categoryids }, true)
c#:
public IHttpActionResult Remove([FromBody] int[] categoryIds)
jQuery:
$.ajax({
type: 'POST',
data: $.param({ '': categoryids }, true),
url: url,
//...
});
The thing with $.param({ '': categoryids }, true) is that it .net will expect post body to contain urlencoded value like =1&=2&=3 without parameter name, and without brackets.
Easy way to send array params to web api
API
public IEnumerable<Category> GetCategories([FromUri]int[] categoryIds){
// code to retrieve categories from database
}
Jquery : send JSON object as request params
$.get('api/categories/GetCategories',{categoryIds:[1,2,3,4]}).done(function(response){
console.log(response);
//success response
});
It will generate your request URL like
../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4
You may try this code for you to take comma separated values / an array of values to get back a JSON from webAPI
public class CategoryController : ApiController
{
public List<Category> Get(String categoryIDs)
{
List<Category> categoryRepo = new List<Category>();
String[] idRepo = categoryIDs.Split(',');
foreach (var id in idRepo)
{
categoryRepo.Add(new Category()
{
CategoryID = id,
CategoryName = String.Format("Category_{0}", id)
});
}
return categoryRepo;
}
}
public class Category
{
public String CategoryID { get; set; }
public String CategoryName { get; set; }
}
Output :
[
{"CategoryID":"4","CategoryName":"Category_4"},
{"CategoryID":"5","CategoryName":"Category_5"},
{"CategoryID":"3","CategoryName":"Category_3"}
]
ASP.NET Core 2.0 Solution (Swagger Ready)
Input
DELETE /api/items/1,2
DELETE /api/items/1
Code
Write the provider (how MVC knows what binder to use)
public class CustomBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(int[]) || context.Metadata.ModelType == typeof(List<int>))
{
return new BinderTypeModelBinder(typeof(CommaDelimitedArrayParameterBinder));
}
return null;
}
}
Write the actual binder (access all sorts of info about the request, action, models, types, whatever)
public class CommaDelimitedArrayParameterBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = bindingContext.ActionContext.RouteData.Values[bindingContext.FieldName] as string;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
var ints = value?.Split(',').Select(int.Parse).ToArray();
bindingContext.Result = ModelBindingResult.Success(ints);
if(bindingContext.ModelType == typeof(List<int>))
{
bindingContext.Result = ModelBindingResult.Success(ints.ToList());
}
return Task.CompletedTask;
}
}
Register it with MVC
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});
Sample usage with a well documented controller for Swagger
/// <summary>
/// Deletes a list of items.
/// </summary>
/// <param name="itemIds">The list of unique identifiers for the items.</param>
/// <returns>The deleted item.</returns>
/// <response code="201">The item was successfully deleted.</response>
/// <response code="400">The item is invalid.</response>
[HttpDelete("{itemIds}", Name = ItemControllerRoute.DeleteItems)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task Delete(List<int> itemIds)
=> await _itemAppService.RemoveRangeAsync(itemIds);
EDIT: Microsoft recommends using a TypeConverter for these kids of operations over this approach. So follow the below posters advice and document your custom type with a SchemaFilter.
Instead of using a custom ModelBinder, you can also use a custom type with a TypeConverter.
[TypeConverter(typeof(StrListConverter))]
public class StrList : List<string>
{
public StrList(IEnumerable<string> collection) : base(collection) {}
}
public class StrListConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
return null;
if (value is string s)
{
if (string.IsNullOrEmpty(s))
return null;
return new StrList(s.Split(','));
}
return base.ConvertFrom(context, culture, value);
}
}
The advantage is that it makes the Web API method's parameters very simple. You dont't even need to specify [FromUri].
public IEnumerable<Category> GetCategories(StrList categoryIds) {
// code to retrieve categories from database
}
This example is for a List of strings, but you could do categoryIds.Select(int.Parse) or simply write an IntList instead.
I originally used the solution that #Mrchief for years (it works great). But when when I added Swagger to my project for API documentation my end point was NOT showing up.
It took me a while, but this is what I came up with. It works with Swagger, and your API method signatures look cleaner:
In the end you can do:
// GET: /api/values/1,2,3,4
[Route("api/values/{ids}")]
public IHttpActionResult GetIds(int[] ids)
{
return Ok(ids);
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Allow WebApi to Use a Custom Parameter Binding
config.ParameterBindingRules.Add(descriptor => descriptor.ParameterType == typeof(int[]) && descriptor.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get)
? new CommaDelimitedArrayParameterBinder(descriptor)
: null);
// Allow ApiExplorer to understand this type (Swagger uses ApiExplorer under the hood)
TypeDescriptor.AddAttributes(typeof(int[]), new TypeConverterAttribute(typeof(StringToIntArrayConverter)));
// Any existing Code ..
}
}
Create a new class: CommaDelimitedArrayParameterBinder.cs
public class CommaDelimitedArrayParameterBinder : HttpParameterBinding, IValueProviderParameterBinding
{
public CommaDelimitedArrayParameterBinder(HttpParameterDescriptor desc)
: base(desc)
{
}
/// <summary>
/// Handles Binding (Converts a comma delimited string into an array of integers)
/// </summary>
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
HttpActionContext actionContext,
CancellationToken cancellationToken)
{
var queryString = actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string;
var ints = queryString?.Split(',').Select(int.Parse).ToArray();
SetValue(actionContext, ints);
return Task.CompletedTask;
}
public IEnumerable<ValueProviderFactory> ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() };
}
Create a new class: StringToIntArrayConverter.cs
public class StringToIntArrayConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
}
Notes:
https://stackoverflow.com/a/47123965/862011 pointed me in the right direction
Swagger was only failing to pick my comma delimited end points when using the [Route] attribute
public class ArrayInputAttribute : ActionFilterAttribute
{
private readonly string[] _ParameterNames;
/// <summary>
///
/// </summary>
public string Separator { get; set; }
/// <summary>
/// cons
/// </summary>
/// <param name="parameterName"></param>
public ArrayInputAttribute(params string[] parameterName)
{
_ParameterNames = parameterName;
Separator = ",";
}
/// <summary>
///
/// </summary>
public void ProcessArrayInput(HttpActionContext actionContext, string parameterName)
{
if (actionContext.ActionArguments.ContainsKey(parameterName))
{
var parameterDescriptor = actionContext.ActionDescriptor.GetParameters().FirstOrDefault(p => p.ParameterName == parameterName);
if (parameterDescriptor != null && parameterDescriptor.ParameterType.IsArray)
{
var type = parameterDescriptor.ParameterType.GetElementType();
var parameters = String.Empty;
if (actionContext.ControllerContext.RouteData.Values.ContainsKey(parameterName))
{
parameters = (string)actionContext.ControllerContext.RouteData.Values[parameterName];
}
else
{
var queryString = actionContext.ControllerContext.Request.RequestUri.ParseQueryString();
if (queryString[parameterName] != null)
{
parameters = queryString[parameterName];
}
}
var values = parameters.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries)
.Select(TypeDescriptor.GetConverter(type).ConvertFromString).ToArray();
var typedValues = Array.CreateInstance(type, values.Length);
values.CopyTo(typedValues, 0);
actionContext.ActionArguments[parameterName] = typedValues;
}
}
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_ParameterNames.ForEach(parameterName => ProcessArrayInput(actionContext, parameterName));
}
}
Usage:
[HttpDelete]
[ArrayInput("tagIDs")]
[Route("api/v1/files/{fileID}/tags/{tagIDs}")]
public HttpResponseMessage RemoveFileTags(Guid fileID, Guid[] tagIDs)
{
_FileRepository.RemoveFileTags(fileID, tagIDs);
return Request.CreateResponse(HttpStatusCode.OK);
}
Request uri
http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
If you want to list/ array of integers easiest way to do this is accept the comma(,) separated list of string and convert it to list of integers.Do not forgot to mention [FromUri] attriubte.your url look like:
...?ID=71&accountID=1,2,3,289,56
public HttpResponseMessage test([FromUri]int ID, [FromUri]string accountID)
{
List<int> accountIdList = new List<int>();
string[] arrAccountId = accountId.Split(new char[] { ',' });
for (var i = 0; i < arrAccountId.Length; i++)
{
try
{
accountIdList.Add(Int32.Parse(arrAccountId[i]));
}
catch (Exception)
{
}
}
}
I have created a custom model binder which converts any comma separated values (only primitive, decimal, float, string) to their corresponding arrays.
public class CommaSeparatedToArrayBinder<T> : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Type type = typeof(T);
if (type.IsPrimitive || type == typeof(Decimal) || type == typeof(String) || type == typeof(float))
{
ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (val == null) return false;
string key = val.RawValue as string;
if (key == null) { bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Wrong value type"); return false; }
string[] values = key.Split(',');
IEnumerable<T> result = this.ConvertToDesiredList(values).ToArray();
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Only primitive, decimal, string and float data types are allowed...");
return false;
}
private IEnumerable<T> ConvertToDesiredArray(string[] values)
{
foreach (string value in values)
{
var val = (T)Convert.ChangeType(value, typeof(T));
yield return val;
}
}
}
And how to use in Controller:
public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
{
return Ok(ids);
}
Make the method type [HttpPost], create a model that has one int[] parameter, and post with json:
/* Model */
public class CategoryRequestModel
{
public int[] Categories { get; set; }
}
/* WebApi */
[HttpPost]
public HttpResponseMessage GetCategories(CategoryRequestModel model)
{
HttpResponseMessage resp = null;
try
{
var categories = //your code to get categories
resp = Request.CreateResponse(HttpStatusCode.OK, categories);
}
catch(Exception ex)
{
resp = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
return resp;
}
/* jQuery */
var ajaxSettings = {
type: 'POST',
url: '/Categories',
data: JSON.serialize({Categories: [1,2,3,4]}),
contentType: 'application/json',
success: function(data, textStatus, jqXHR)
{
//get categories from data
}
};
$.ajax(ajaxSettings);
Or you could just pass a string of delimited items and put it into an array or list on the receiving end.
I addressed this issue this way.
I used a post message to the api to send the list of integers as data.
Then I returned the data as an ienumerable.
The sending code is as follows:
public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
IEnumerable<Contact> result = null;
if (ids!=null&&ids.Count()>0)
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49520/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
String _endPoint = "api/" + typeof(Contact).Name + "/ListArray";
HttpResponseMessage response = client.PostAsJsonAsync<IEnumerable<int>>(_endPoint, ids).Result;
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
result = JsonConvert.DeserializeObject<IEnumerable<Contact>>(response.Content.ReadAsStringAsync().Result);
}
}
}
catch (Exception)
{
}
}
return result;
}
The receiving code is as follows:
// POST api/<controller>
[HttpPost]
[ActionName("ListArray")]
public IEnumerable<Contact> Post([FromBody]IEnumerable<int> ids)
{
IEnumerable<Contact> result = null;
if (ids != null && ids.Count() > 0)
{
return contactRepository.Fill(ids);
}
return result;
}
It works just fine for one record or many records. The fill is an overloaded method using DapperExtensions:
public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
IEnumerable<Contact> result = null;
if (ids != null && ids.Count() > 0)
{
using (IDbConnection dbConnection = ConnectionProvider.OpenConnection())
{
dbConnection.Open();
var predicate = Predicates.Field<Contact>(f => f.id, Operator.Eq, ids);
result = dbConnection.GetList<Contact>(predicate);
dbConnection.Close();
}
}
return result;
}
This allows you to fetch data from a composite table (the id list), and then return the records you are really interested in from the target table.
You could do the same with a view, but this gives you a little more control and flexibility.
In addition, the details of what you are seeking from the database are not shown in the query string. You also do not have to convert from a csv file.
You have to keep in mind when using any tool like the web api 2.x interface is that the get, put, post, delete, head, etc., functions have a general use, but are not restricted to that use.
So, while post is generally used in a create context in the web api interface, it is not restricted to that use. It is a regular html call that can be used for any purpose permitted by html practice.
In addition, the details of what is going on are hidden from those "prying eyes" we hear so much about these days.
The flexibility in naming conventions in the web api 2.x interface and use of regular web calling means you send a call to the web api that misleads snoopers into thinking you are really doing something else. You can use "POST" to really retrieve data, for example.
My solution was to create an attribute to validate strings, it does a bunch of extra common features, including regex validation that you can use to check for numbers only and then later I convert to integers as needed...
This is how you use:
public class MustBeListAndContainAttribute : ValidationAttribute
{
private Regex regex = null;
public bool RemoveDuplicates { get; }
public string Separator { get; }
public int MinimumItems { get; }
public int MaximumItems { get; }
public MustBeListAndContainAttribute(string regexEachItem,
int minimumItems = 1,
int maximumItems = 0,
string separator = ",",
bool removeDuplicates = false) : base()
{
this.MinimumItems = minimumItems;
this.MaximumItems = maximumItems;
this.Separator = separator;
this.RemoveDuplicates = removeDuplicates;
if (!string.IsNullOrEmpty(regexEachItem))
regex = new Regex(regexEachItem, RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var listOfdValues = (value as List<string>)?[0];
if (string.IsNullOrWhiteSpace(listOfdValues))
{
if (MinimumItems > 0)
return new ValidationResult(this.ErrorMessage);
else
return null;
};
var list = new List<string>();
list.AddRange(listOfdValues.Split(new[] { Separator }, System.StringSplitOptions.RemoveEmptyEntries));
if (RemoveDuplicates) list = list.Distinct().ToList();
var prop = validationContext.ObjectType.GetProperty(validationContext.MemberName);
prop.SetValue(validationContext.ObjectInstance, list);
value = list;
if (regex != null)
if (list.Any(c => string.IsNullOrWhiteSpace(c) || !regex.IsMatch(c)))
return new ValidationResult(this.ErrorMessage);
return null;
}
}
I just added the Query key (Refit lib) in the property for the request.
[Query(CollectionFormat.Multi)]
public class ExampleRequest
{
[FromQuery(Name = "name")]
public string Name { get; set; }
[AliasAs("category")]
[Query(CollectionFormat.Multi)]
public List<string> Categories { get; set; }
}
All other solutions need too much work. I was trying to use IEnumerable<long> or long[] in a HttpGet method parameter, but I see no point of doing all the work just to make the signature of the handler method parameter long[]. I ended up just making it string, and then separated it within the handler. Took me one line.
public async Task<IActionResult> SomeHandler(string idsString)
{
var ids = idsString.Split(',').Select(x => long.Parse(x));
Now you can just pass the numbers like
.../SomeHandler?idsString=123,456,789,012
i have to iterate a loop on about 400 different XML files and every time i will be getting different xml file.
I have about 11 nodes in the XML(all coming as String) and i am parsing this XML and storing the XML Element's values using Entity Framework in the Database (in different data types like Decimal, int, string, double)
I do not know which xml node will come as null and i do not want to add a null check for each and every node..
Is there a way to implement a common null check for the whole XML file in the loop so if any node comes as null, i can assign it to the default value of respective data type in its respective Entity.. Some thing like the code snippet shown below:-
foreach (XmlNode node in tableElements)
{
dcSearchTerm searchTermEntity = new dcSearchTerm();
//Reference keywords: creation & assignment
int IDRef = 0, salesRef = 0, visitsRef = 0, saleItemsRef = 0;
DateTime visitDateRef = new DateTime();
decimal revenueRef = 0;
int.TryParse(node["id"].InnerText, out IDRef);
searchTermEntity.SearchTerm = node["Search_x0020_Term"].InnerText;
searchTermEntity.ReferrerDomain = node["Referrer_x0020_Domain"].InnerText;
if (node["Country"] == null)
{
searchTermEntity.Country = "";
}
else
{
searchTermEntity.Country = node["Country"].InnerText;
}
DateTime.TryParse(node["Visit_x0020_Date"].InnerText, out visitDateRef);
searchTermEntity.VisitEntryPage = node["Visit_x0020_Entry_x0020_Page"].InnerText;
int.TryParse(node["Sales"].InnerText, out salesRef);
int.TryParse(node["Visits"].InnerText, out visitsRef);
decimal.TryParse(node["Revenue"].InnerText, out revenueRef);
int.TryParse(node["Sale_x0020_Items"].InnerText, out saleItemsRef);
// assigning reference values to the entity
searchTermEntity.ID = IDRef;
searchTermEntity.VisitDate = visitDateRef;
searchTermEntity.Sales = salesRef;
searchTermEntity.Visits = visitsRef;
searchTermEntity.Revenue = revenueRef;
searchTermEntity.SaleItems = saleItemsRef;
searches.Add(searchTermEntity);
return searches;
}
P.S.:- This is my first question on SO, please feel free to ask more details
Waiting for a flood of suggestions ! :)
OK, here is extension class that adds methods to Strings and XmlNodes:
public static class MyExtensions
{
// obviously these ToType methods can be implemented with generics
// to further reduce code duplication
public static int ToInt32(this string value)
{
Int32 result = 0;
if (!string.IsNullOrEmpty(value))
Int32.TryParse(value, out result);
return result;
}
public static decimal ToDecimal(this string value)
{
Decimal result = 0M;
if (!string.IsNullOrEmpty(value))
Decimal.TryParse(value, out result);
return result;
}
public static int GetInt(this XmlNode node, string key)
{
var str = node.GetString(key);
return str.ToInt32();
}
public static string GetString(this XmlNode node, string key)
{
if (node[key] == null || String.IsNullOrEmpty(node[key].InnerText))
return null;
else
return node.InnerText;
}
// implement GetDateTime/GetDecimal as practice ;)
}
Now we can rewrite your code like:
foreach (XmlNode node in tableElements)
{
// DECLARE VARIABLES WHEN YOU USE THEM
// DO NOT DECLARE THEM ALL AT THE START OF YOUR METHOD
// http://programmers.stackexchange.com/questions/56585/where-do-you-declare-variables-the-top-of-a-method-or-when-you-need-them
dcSearchTerm searchTermEntity = new dcSearchTerm()
{
ID = node.GetInt("id"),
SearchTerm = node.GetString("Search_x0020_Term"),
ReferrerDomain = node.GetString("Referrer_x0020_Domain"),
Country = node.GetString("Country"),
VisitDate = node.GetDateTime("Visit_x0020_Date"),
VisitEntryPage = node.GetString("Visit_x0020_Entry_x0020_Page"),
Sales = node.GetInt("Sales"),
Visits = node.GetInt("Visits"),
Revenue = node.GetDecimal("Revenue"),
SaleItems = node.GetDecimal("Sale_x0020_Items")
};
searches.Add(searchTermEntity);
return searches;
}
Don't forget to implement GetDateTime and GetDecimal extensions- I've left those to you ;).
You can use a monad style extension method like below. The sample provided acts only on structs. You can modify it to use for all types.
public static class NullExtensions
{
public delegate bool TryGetValue<T>(string input, out T value);
public static T DefaultIfNull<T>(this string value, TryGetValue<T> evaluator, T defaultValue) where T : struct
{
T result;
if (evaluator(value, out result))
return result;
return defaultValue;
}
public static T DefaultIfNull<T>(this string value, TryGetValue<T> evaluator) where T : struct
{
return value.DefaultIfNull(evaluator, default(T));
}
}
Example:
string s = null;
bool result = s.DefaultIfNull<bool>(bool.TryParse, true);
int r = s.DefaultIfNull<int>(int.TryParse);
Im looking for a way to create CSV from all class instances.
What i want is that i could export ANY class (all of its instances) to CSV.
Can some1 direct me to possible solution for this (in case already anwsered).
thanx !
Have a look at LINQ to CSV. Although it's a little on the heavy side, which is why I wrote the following code to perform just the small subset of functionality that I needed. It handles both properties and fields, like you asked for, although not much else. One thing it does do is properly escape the output in case it contains commas, quotes, or newline characters.
public static class CsvSerializer {
/// <summary>
/// Serialize objects to Comma Separated Value (CSV) format [1].
///
/// Rather than try to serialize arbitrarily complex types with this
/// function, it is better, given type A, to specify a new type, A'.
/// Have the constructor of A' accept an object of type A, then assign
/// the relevant values to appropriately named fields or properties on
/// the A' object.
///
/// [1] http://tools.ietf.org/html/rfc4180
/// </summary>
public static void Serialize<T>(TextWriter output, IEnumerable<T> objects) {
var fields =
from mi in typeof (T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
where new [] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
let orderAttr = (ColumnOrderAttribute) Attribute.GetCustomAttribute(mi, typeof (ColumnOrderAttribute))
orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
select mi;
output.WriteLine(QuoteRecord(fields.Select(f => f.Name)));
foreach (var record in objects) {
output.WriteLine(QuoteRecord(FormatObject(fields, record)));
}
}
static IEnumerable<string> FormatObject<T>(IEnumerable<MemberInfo> fields, T record) {
foreach (var field in fields) {
if (field is FieldInfo) {
var fi = (FieldInfo) field;
yield return Convert.ToString(fi.GetValue(record));
} else if (field is PropertyInfo) {
var pi = (PropertyInfo) field;
yield return Convert.ToString(pi.GetValue(record, null));
} else {
throw new Exception("Unhandled case.");
}
}
}
const string CsvSeparator = ",";
static string QuoteRecord(IEnumerable<string> record) {
return String.Join(CsvSeparator, record.Select(field => QuoteField(field)).ToArray());
}
static string QuoteField(string field) {
if (String.IsNullOrEmpty(field)) {
return "\"\"";
} else if (field.Contains(CsvSeparator) || field.Contains("\"") || field.Contains("\r") || field.Contains("\n")) {
return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
} else {
return field;
}
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ColumnOrderAttribute : Attribute {
public int Order { get; private set; }
public ColumnOrderAttribute(int order) { Order = order; }
}
}
Actually, something similar has been addressed here:
Best practices for serializing objects to a custom string format for use in an output file
Is this useful to you?
There is a sample that uses reflection to pull out the field names and values and append them to a string.
You can use reflection to traverse all the class properties/fields and write them to CSV.
A better approach would be to define a custom attribute and decorate the members you want to export and only export those attributes.
I am separating my answer into two sections:
The first one is how to export some generic item list into csv, with encoding, headers - (it will build csv data only for specified headers, and will ignore unneeded properties).
public string ExportCsv<T>(IEnumerable<T> items, Dictionary<string, string> headers)
{
string result;
using (TextWriter textWriter = new StreamWriter(myStream, myEncoding))
{
result = this.WriteDataAsCsvWriter<T>(items, textWriter, headers);
}
return result;
}
private string WriteDataAsCsvWriter<T>(IEnumerable<T> items, TextWriter textWriter, Dictionary<string, string> headers)
{
//Add null validation
////print the columns headers
StringBuilder sb = new StringBuilder();
//Headers
foreach (KeyValuePair<string, string> kvp in headers)
{
sb.Append(ToCsv(kvp.Value));
sb.Append(",");
}
sb.Remove(sb.Length - 1, 1);//the last ','
sb.Append(Environment.NewLine);
//the values
foreach (var item in items)
{
try
{
Dictionary<string, string> values = GetPropertiesValues(item, headers);
foreach (var value in values)
{
sb.Append(ToCsv(value.Value));
sb.Append(",");
}
sb.Remove(sb.Length - 1, 1);//the last ','
sb.Append(Environment.NewLine);
}
catch (Exception e1)
{
//do something
}
}
textWriter.Write(sb.ToString());
return sb.ToString();
}
//Help function that encode text to csv:
public static string ToCsv(string input)
{
if (input != null)
{
input = input.Replace("\r\n", string.Empty)
.Replace("\r", string.Empty)
.Replace("\n", string.Empty);
if (input.Contains("\""))
{
input = input.Replace("\"", "\"\"");
}
input = "\"" + input + "\"";
}
return input;
}
This is the most important function, Its extracting the properties values out of (almost) any generic class.
private Dictionary<string, string> GetPropertiesValues(object item, Dictionary<string, string> headers)
{
Dictionary<string, string> values = new Dictionary<string, string>();
if (item == null)
{
return values;
}
//We need to make sure each value is coordinated with the headers, empty string
foreach (var key in headers.Keys)
{
values[key] = String.Empty;
}
Type t = item.GetType();
PropertyInfo[] propertiesInfo = t.GetProperties();
foreach (PropertyInfo propertiyInfo in propertiesInfo)
{
//it not complex: string, int, bool, Enum
if ((propertiyInfo.PropertyType.Module.ScopeName == "CommonLanguageRuntimeLibrary") || propertiyInfo.PropertyType.IsEnum)
{
if (headers.ContainsKey(propertiyInfo.Name))
{
var value = propertiyInfo.GetValue(item, null);
if (value != null)
{
values[propertiyInfo.Name] = value.ToString();
}
}
}
else//It's complex property
{
if (propertiyInfo.GetIndexParameters().Length == 0)
{
Dictionary<string, string> lst = GetPropertiesValues(propertiyInfo.GetValue(item, null), headers);
foreach (var value in lst)
{
if (!string.IsNullOrEmpty(value.Value))
{
values[value.Key] = value.Value;
}
}
}
}
}
return values;
}
Example for GetPropertiesValues:
public MyClass
{
public string Name {get; set;}
public MyEnum Type {get; set;}
public MyClass2 Child {get; set;}
}
public MyClass2
{
public int Age {get; set;}
public DateTime MyDate {get; set;}
}
MyClass myClass = new MyClass()
{
Name = "Bruce",
Type = MyEnum.Sometype,
Child = new MyClass2()
{
Age = 18,
MyDate = DateTime.Now()
}
};
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("Name", "CustomCaption_Name");
headers.Add("Type", "CustomCaption_Type");
headers.Add("Age", "CustomCaption_Age");
GetPropertiesValues(myClass, headers)); // OUTPUT: {{"Name","Bruce"},{"Type","Sometype"},{"Age","18"}}
My answer is based on Michael Kropat's answer from above.
I added two functions to his answer because it didn't want to write straight to file as I still had some further processing to do. Instead I wanted the header information separate to the values so I could put everything back together later.
public static string ToCsvString<T>(T obj)
{
var fields =
from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
select mi;
return QuoteRecord(FormatObject(fields, obj));
}
public static string GetCsvHeader<T>(T obj)
{
var fields =
from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
select mi;
return QuoteRecord(fields.Select(f => f.Name));
}