make in the URL the controllername visible but not the parameters - asp.net-mvc-3

Is there a way to create an URL with only the name of the controller but not its parameters in MVC3
I do an #url.Action("action","controller",the model) an I want that the user see;
'/controller/action' not '/controller/action?...&..&..'
I have no idea how i have to do this.

You can use MVC contrlib. I did something similar without the need for contrlib
public static class UrlExtensions
{
public static string Action<TController>(this UrlHelper urlHelper, Expression<Action<TController>> expression) where TController : Controller
{
return BuildUrlFromExpression(urlHelper.RequestContext, urlHelper.RouteCollection, expression);
}
public static string ActionWithParameters<TController>(this UrlHelper urlHelper, Expression<Action<TController>> expression) where TController : Controller
{
return BuildUrlFromExpressionWithParameters(urlHelper.RequestContext, urlHelper.RouteCollection, expression);
}
public static RedirectToRouteResult Redirect<TController>(this TController controller, Expression<Action<TController>> expression) where TController : Controller
{
var valuesFromExpression = ExpressionHelper.GetRouteValuesFromExpressionWithParameters(expression);
return new RedirectToRouteResult(valuesFromExpression);
}
private static string BuildUrlFromExpression<TController>(RequestContext context, RouteCollection routeCollection, Expression<Action<TController>> action) where TController : Controller
{
var valuesFromExpression = ExpressionHelper.GetRouteValuesFromExpression(action);
var virtualPathForArea = RouteCollectionExtensions.GetVirtualPathForArea(routeCollection, context, valuesFromExpression);
if (virtualPathForArea != null)
return virtualPathForArea.VirtualPath;
return null;
}
private static string BuildUrlFromExpressionWithParameters<TController>(RequestContext context, RouteCollection routeCollection, Expression<Action<TController>> action) where TController : Controller
{
var valuesFromExpression = ExpressionHelper.GetRouteValuesFromExpressionWithParameters(action);
var virtualPathForArea = RouteCollectionExtensions.GetVirtualPathForArea(routeCollection, context, valuesFromExpression);
if (virtualPathForArea != null)
return virtualPathForArea.VirtualPath;
return null;
}
}
public static class ExpressionHelper
{
public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller
{
MethodCallExpression call;
return GetRouteValuesFromExpression(action, out call);
}
public static RouteValueDictionary GetRouteValuesFromExpressionWithParameters<TController>(Expression<Action<TController>> action) where TController : Controller
{
MethodCallExpression call;
var rvd = GetRouteValuesFromExpression(action, out call);
AddParameterValuesFromExpressionToDictionary(rvd, call);
return rvd;
}
private static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action, out MethodCallExpression call) where TController : Controller
{
if (action == null)
throw new ArgumentNullException("action");
call = action.Body as MethodCallExpression;
if (call == null)
throw new ArgumentException("MustBeMethodCall", "action");
string name = typeof(TController).Name;
if (!name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException("TargetMustEndInController", "action");
string str = name.Substring(0, name.Length - "Controller".Length);
if (str.Length == 0)
throw new ArgumentException("CannotRouteToController", "action");
string targetActionName = GetTargetActionName(call.Method);
var rvd = new RouteValueDictionary();
rvd.Add("Controller", (object)str);
rvd.Add("Action", (object)targetActionName);
var linkAreaAttribute = typeof(TController).GetCustomAttributes(typeof(ActionLinkAreaAttribute), true).FirstOrDefault() as ActionLinkAreaAttribute;
if (linkAreaAttribute != null)
{
string area = linkAreaAttribute.Area;
rvd.Add("Area", (object)area);
}
return rvd;
}
public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
{
if (expression.Body.NodeType == ExpressionType.Call)
return GetInputName((MethodCallExpression)expression.Body).Substring(expression.Parameters[0].Name.Length + 1);
else
return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
}
public static PropertyInfo GetPropertyInfo<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
{
var member = expression.Body as MemberExpression;
if (member == null)
return null;
var propInfo = member.Member as PropertyInfo;
return propInfo;
}
private static string GetInputName(MethodCallExpression expression)
{
var expression1 = expression.Object as MethodCallExpression;
if (expression1 != null)
return GetInputName(expression1);
else
return expression.Object.ToString();
}
private static string GetTargetActionName(MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException("methodInfo");
var name = methodInfo.Name;
if (methodInfo.IsDefined(typeof (NonActionAttribute), true))
{
throw new InvalidOperationException(string.Format(Helpers.MyFormat, "CannotCallNonAction {0}", name));
}
var actionNameAttribute = methodInfo.GetCustomAttributes(typeof (ActionNameAttribute), true).OfType<ActionNameAttribute>().FirstOrDefault();
if (actionNameAttribute != null)
return actionNameAttribute.Name;
if (methodInfo.DeclaringType.IsSubclassOf(typeof (AsyncController)))
{
if (name.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
return name.Substring(0, name.Length - "Async".Length);
if (name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(string.Format(Helpers.MyFormat, "CannotCallCompletedMethod: {0}", name));
}
return name;
}
private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
{
var parameters = call.Method.GetParameters();
if (parameters.Length <= 0)
return;
for (int index = 0; index < parameters.Length; ++index)
{
var expression = call.Arguments[index];
var constantExpression = expression as ConstantExpression;
var obj = constantExpression == null ? CachedExpressionCompiler.Evaluate(expression) : constantExpression.Value;
rvd.Add(parameters[index].Name, obj);
}
}
}
From your view you can now do, the default values has to be passed for the LINQ expression to work, but they wil not be rendered
#(Url.Action<MyController>(x => x.MethodName(null, 0, 0)))

Related

ASP.NET MVC validation return lowercase property name

In my ASP.NET MVC Core web application the Json serialization of properties is set to camel case (with first letter lowercase):
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };
opt.SerializerSettings.Converters.Add(new StringEnumConverter(true));
});
The serialization to the client is working as expected.
But when the javascript client tries to post data and this data is not valid, he receives a validation message with capital letter properties, this validation messages are the ModelState:
{"Info":["The Info field is required."]}
Is there a way to make ASP.NET return lowercase property in validation messages of the ModelState to reflect the naming strategy?
The solution is to disable the automatic api validation filter and create own json result with the validation messages:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
And in the controller:
protected ActionResult ValidationFailed()
{
var errorList = ModelState.ToDictionary(
kvp => kvp.Key.ToCamelCase(),
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
return BadRequest(errorList);
}
public async Task<ActionResult> Create([FromBody]TCreateDto model)
{
if (ModelState.IsValid == false)
{
return ValidationFailed();
}
...
}
The string helper method:
public static string ToCamelCase(this string name)
{
if (string.IsNullOrEmpty(name))
{
return name;
}
return name.Substring(0, 1).ToLower() + name.Substring(1);
}
There is an easier solution. Use Fluent Validator's ValidatorOptions.Global.PropertyNameResolver. Taken from here and converted to C# 8 and Fluent Validation 9:
In Startup.cs, ConfigureServices use:
services
.AddControllers()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<MyValidator>();
// Convert property names to camelCase as Asp.Net Core does https://github.com/FluentValidation/FluentValidation/issues/226
ValidatorOptions.Global.PropertyNameResolver = CamelCasePropertyNameResolver.ResolvePropertyName;
})
.AddNewtonsoftJson(NewtonsoftUtils.SetupNewtonsoftOptionsDefaults);
and resolver itself:
/// <summary>
/// Convert property names to camelCase as Asp.Net Core does
/// https://github.com/FluentValidation/FluentValidation/issues/226
/// </summary>
public class CamelCasePropertyNameResolver
{
public static string? ResolvePropertyName(Type type, MemberInfo memberInfo, LambdaExpression expression)
{
return ToCamelCase(DefaultPropertyNameResolver(type, memberInfo, expression));
}
private static string? DefaultPropertyNameResolver(Type type, MemberInfo memberInfo, LambdaExpression expression)
{
if (expression != null)
{
var chain = PropertyChain.FromExpression(expression);
if (chain.Count > 0)
{
return chain.ToString();
}
}
if (memberInfo != null)
{
return memberInfo.Name;
}
return null;
}
private static string? ToCamelCase(string? s)
{
if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0]))
{
return s;
}
var chars = s.ToCharArray();
for (var i = 0; i < chars.Length; i++)
{
if (i == 1 && !char.IsUpper(chars[i]))
{
break;
}
var hasNext = (i + 1 < chars.Length);
if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
{
break;
}
chars[i] = char.ToLower(chars[i], CultureInfo.InvariantCulture);
}
return new string(chars);
}
}
I have faced the same issue. I have overridden DefaultProblemDetailsFactory.cs from the source code and add logic to change the first letters in the 'errors' dictionary.
Steps:
1 - Create new CustomProblemDetailsFactory.cs class:
internal sealed class CustomProblemDetailsFactory : ProblemDetailsFactory
{
private readonly ApiBehaviorOptions _options;
public CustomProblemDetailsFactory(IOptions<ApiBehaviorOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public override ProblemDetails CreateProblemDetails(
HttpContext httpContext,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null)
{
statusCode ??= 500;
var problemDetails = new ProblemDetails
{
Status = statusCode,
Title = title,
Type = type,
Detail = detail,
Instance = instance,
};
ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value);
return problemDetails;
}
public override ValidationProblemDetails CreateValidationProblemDetails(
HttpContext httpContext,
ModelStateDictionary modelStateDictionary,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null)
{
if (modelStateDictionary == null)
{
throw new ArgumentNullException(nameof(modelStateDictionary));
}
statusCode ??= 400;
var problemDetails = new ValidationProblemDetails(modelStateDictionary)
{
Status = statusCode,
Type = type,
Detail = detail,
Instance = instance,
};
if (title != null)
{
// For validation problem details, don't overwrite the default title with null.
problemDetails.Title = title;
}
// FIX LOWERCASE, MAKE THE FIRST LETTERS LOWERCASE
///-----------------------------
if (problemDetails.Errors != null)
{
var newErrors = problemDetails.Errors.ToDictionary(x => this.MakeFirstLetterLowercase(x.Key), x => x.Value);
problemDetails.Errors.Clear();
foreach (var keyValue in newErrors)
{
problemDetails.Errors.Add(keyValue.Key, keyValue.Value);
}
}
///-----------------------------
ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value);
return problemDetails;
}
private void ApplyProblemDetailsDefaults(HttpContext httpContext, ProblemDetails problemDetails, int statusCode)
{
problemDetails.Status ??= statusCode;
if (_options.ClientErrorMapping.TryGetValue(statusCode, out var clientErrorData))
{
problemDetails.Title ??= clientErrorData.Title;
problemDetails.Type ??= clientErrorData.Link;
}
var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
if (traceId != null)
{
problemDetails.Extensions["traceId"] = traceId;
}
}
private string MakeFirstLetterLowercase(string str)
{
if (!string.IsNullOrEmpty(str) && char.IsUpper(str[0]))
{
return str.Length == 1 ? char.ToLower(str[0]).ToString() : char.ToLower(str[0]) + str[1..];
}
return str;
}
}
2 - In the Startup.cs override the default ProblemDetailsFactory:
services.AddSingleton<ProblemDetailsFactory, CustomProblemDetailsFactory>();
After that all keys in the dictionary 'errors' will start with lowercase

Exception in HttpControllerDispatcher

In my WebApiConfig::Register(...) I replaced the HttpControllerSelector with my own controller selector. When I fire off a POST request the SelectController member is correctly called and I return a ControllerDescriptor with the correct type of my controller.
But then HttpControllerDispatcher is raising an exception saying "The given was not present in the dictionary." Anyone has an idea how to debug such error?
The complete exception is message is:
The given key was not present in the dictionary.","ExceptionType":"System.Collections.Generic.KeyNotFoundException","StackTrace":" at System.Collections.Generic.Dictionary`2.get_Item(TKey key)\r\n
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.FindActionMatchRequiredRouteAndQueryParameters(IEnumerable`1 candidatesFound)\r\n
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.FindMatchingActions(HttpControllerContext controllerContext, Boolean ignoreVerbs)\r\n
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n
at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n
at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
And here is my Controller Selector:
public class Namespace_HTTP_Controller_Selector : IHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controller;
public Namespace_HTTP_Controller_Selector(HttpConfiguration Config)
{
_configuration = Config;
_controller = new Lazy<Dictionary<string, HttpControllerDescriptor>>(Initialize_Controller_Dictionary);
}
public HttpControllerDescriptor SelectController(HttpRequestMessage Request)
{
var Route_Data = Request.GetRouteData();
if(Route_Data == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var Controller_Name = Get_Controller_Name(Route_Data);
if(Controller_Name == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var Name_Space = Get_Version(Route_Data);
if (Name_Space == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var Controller_Key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", Name_Space, Controller_Name);
HttpControllerDescriptor Descriptor;
if(_controller.Value.TryGetValue(Controller_Key, out Descriptor))
{
return Descriptor;
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
return _controller.Value;
}
private Dictionary<string, HttpControllerDescriptor> Initialize_Controller_Dictionary()
{
var Dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
var Assemblies_Resolver = _configuration.Services.GetAssembliesResolver();
var Controller_Resolver = _configuration.Services.GetHttpControllerTypeResolver();
var Controller_Types = Controller_Resolver.GetControllerTypes(Assemblies_Resolver);
foreach(var ct in Controller_Types)
{
var Segments = ct.Namespace.Split(Type.Delimiter);
var Controller_Name = ct.Name.Remove(ct.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var Controller_Key = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", Segments[Segments.Length - 1], Controller_Name);
if(Dictionary.Keys.Contains(Controller_Key) == false)
{
Dictionary[Controller_Key] = new HttpControllerDescriptor(_configuration, ct.Name, ct);
}
}
return Dictionary;
}
private T Get_Route_Variable<T>(IHttpRouteData Route_Data, string Name)
{
object Result;
if(Route_Data.Values.TryGetValue(Name, out Result))
{
return (T)Result;
}
return default(T);
}
private string Get_Controller_Name(IHttpRouteData Route_Data)
{
var SubRoute = Route_Data.GetSubRoutes().FirstOrDefault();
if( SubRoute == null )
{
return null;
}
var Data_Token_Value = SubRoute.Route.DataTokens.First().Value;
if(Data_Token_Value == null)
{
return null;
}
var Controller_Name = ((HttpActionDescriptor[])Data_Token_Value).First().ControllerDescriptor.ControllerName.Replace("Controller", string.Empty);
return Controller_Name;
}
private string Get_Version(IHttpRouteData Route_Data)
{
var Sub_Route_Data = Route_Data.GetSubRoutes().FirstOrDefault();
if(Sub_Route_Data== null)
{
return null;
}
return Get_Route_Variable<string>(Sub_Route_Data, "apiVersion");
}
}
This is because you are not setting subroute data in request before returning descriptor.
HttpControllerDescriptor Descriptor;
if(_controller.Value.TryGetValue(Controller_Key, out Descriptor))
{
var subRoutes = Route_Data.GetSubRoutes();
IEnumerable<IHttpRouteData> filteredSubRoutes = subRoutes.Where(attrRouteData =>
{
HttpControllerDescriptor currentDescriptor = ((HttpActionDescriptor[])Route_Data.Route.DataTokens["actions"]).First().ControllerDescriptor;
return currentDescriptor != null && currentDescriptor.ControllerName.Equals(Descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
});
Route_Data.Values["MS_SubRoutes"] = filteredSubRoutes.ToArray();
return Descriptor;
}
Take a look at:
Versioning ASP.NET Web API 2 with Media Types
public class CustomSelectorController : DefaultHttpControllerSelector
{
HttpConfiguration _config;
public CustomSelectorController(HttpConfiguration config) : base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
IEnumerable<IHttpRouteData> attributeSubRoutes = routeData.GetSubRoutes();
var actions = attributeSubRoutes.LastOrDefault()?.Route?.DataTokens["actions"] as HttpActionDescriptor[];
var controllerName = "";
if (actions != null && actions.Length > 0)
{
controllerName = actions[0].ControllerDescriptor.ControllerName;
}
IEnumerable<string> headerValues = null;
//Custom Header Name to be check version
if (request.Headers.TryGetValues("Accept-Version", out headerValues))
{
var apiVersion = headerValues.First().ToUpper();
if (apiVersion == "V2")
{
controllerName = controllerName + apiVersion;
}
}
HttpControllerDescriptor controllerDescriptor = null;
IEnumerable<IHttpRouteData> filteredSubRoutes = attributeSubRoutes.Where(attrRouteData =>
{
HttpControllerDescriptor currentDescriptor = GetControllerDescriptor(attrRouteData);
bool match = currentDescriptor.ControllerName.Equals(controllerName);
if (match && (controllerDescriptor == null))
{
controllerDescriptor = currentDescriptor;
}
return match;
});
routeData.Values["MS_SubRoutes"] = filteredSubRoutes.ToArray();
return controllerDescriptor;
}
private HttpControllerDescriptor GetControllerDescriptor(IHttpRouteData routeData)
{
return ((HttpActionDescriptor[])routeData.Route.DataTokens["actions"]).First().ControllerDescriptor;
}
}

How do I apply a default IComparable<T> in a Linq OrderBy clause

I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}

binaryexpression contains method

I created a helper class which is able to build lambda expression from string parameters an I can filter a query result using this.
But I have little problem that the LINQ.Expressions.Expression does not have a Contains method.
this is my code:
string member = d.Member;
object value = d.Value;
System.Linq.Expressions.Expression expression = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
foreach (var property in member.Split('.'))
{
expression = System.Linq.Expressions.Expression.PropertyOrField(expression, property);
}
ConstantExpression c = System.Linq.Expressions.Expression.Constant(value, typeof(string));
BinaryExpression b = null;
switch (d.Operator)
{
case FilterOperator.IsEqualTo:
b = System.Linq.Expressions.Expression.Equal(expression, c);
break;
case FilterOperator.Contains:
b = GetExpression<T>(expression.ToString(), value.ToString()).Body as BinaryExpression;
break;
case FilterOperator.IsGreaterThanOrEqualTo:
b = System.Linq.Expressions.Expression.GreaterThanOrEqual(expression, c);
break;
case FilterOperator.IsLessThanOrEqualTo:
b = System.Linq.Expressions.Expression.LessThanOrEqual(expression, c);
break;
}
CriteriaCollection.Add(b);
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return BinaryExpression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
It should be work but the how I can convert Expression to BinaryExpression?
Anybody knows this or knows an other solution which is working?
i know it's been a long time since the question was asked but, well, i've think i've found an answer, which, basically, lies on MakeBinary.
First of all, i had to create a method alike Contains, but not a self referencing extension method, like this:
public static bool Containss(string text, string text2)
{
return text.Contains(text2, StringComparison.OrdinalIgnoreCase);
}
Then you use it like this:
MethodInfo method = typeof(StackOverflowAnswer).GetMethod("Containss", new[] { typeof(string), typeof(string) });
var constainsExp = Expression.MakeBinary(ExpressionType.Equal, Expression.Constant("FULL TEXT"), Expression.Constant("TEXT"), false, method);
Something that may also help, considering your propable objective, is to receive the member, parameter and value to compose your final expression. Here's an example:
class StackOverflowAnswer
{
static void Main()
{
ParameterExpression parameter = Expression.Parameter(typeof(Student), typeof(Student).Name);
var exp1 = GetBinaryExpression(parameter, "Name", "Foo");
var exp2 = GetBinaryExpression(parameter, "Name", "Bar");
BinaryExpression[] expressions = new BinaryExpression[] { exp1, exp2 };
var bin = CombineExpressions(expressions, parameter);
var func = Expression.Lambda<Func<Student, bool>>(bin, parameter);
var exp = func.Compile();
Student student = new Student { Name = "Foo Bar" };
var x = exp(student);
Console.WriteLine(x);
}
public static BinaryExpression GetBinaryExpression(ParameterExpression parameter, string property, object comparissonValue)
{
MemberExpression member = Expression.Property(parameter, property);
ConstantExpression constant = Expression.Constant(comparissonValue, comparissonValue.GetType());
MethodInfo method = typeof(StackOverflowAnswer).GetMethod("Containss", new[] { typeof(string), typeof(string) });
var containsExp = Expression.MakeBinary(ExpressionType.Equal, member, constant, false, method);
return containsExp ;
}
public static BinaryExpression CombineExpressions(BinaryExpression[] expressions, ParameterExpression parameter)
{
bool first = true;
BinaryExpression expFull = expressions[0];
foreach (BinaryExpression item in expressions)
{
if (first)
first = false;
else
{
expFull = Expression.AndAlso(expFull, item);
}
}
return expFull;
}
internal class Student
{
public string Name { get; set; }
}
}
public class FilterExpressionHelper<T> where T : class
{
public FilterExpressionHelper()
{
CriteriaCollection = new List<BinaryExpression>();
}
public List<BinaryExpression> CriteriaCollection { get; set; }
public Expression<Func<T, bool>> NoFilterExpression { get; set; }
public void RemoveFilterCriteriaFilterDescriptor(Telerik.Windows.Data.FilterDescriptor d)
{
string member = d.Member;
object value = d.Value;
System.Linq.Expressions.Expression expression = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
foreach (var property in member.Split('.'))
{
expression = System.Linq.Expressions.Expression.PropertyOrField(expression, property);
}
ConstantExpression c = System.Linq.Expressions.Expression.Constant(value, typeof(string));
BinaryExpression b = System.Linq.Expressions.Expression.Equal(expression, c);
BinaryExpression expr = CriteriaCollection.Where(cr => cr.Right.ToString() == b.Right.ToString()).FirstOrDefault();
CriteriaCollection.Remove(expr);
}
public void AddFilterCriteriaFilterDescriptor(Telerik.Windows.Data.FilterDescriptor d)
{
string member = d.Member;
object value = d.Value;
System.Linq.Expressions.Expression expression = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
foreach (var property in member.Split('.'))
{
expression = System.Linq.Expressions.Expression.PropertyOrField(expression, property);
}
ConstantExpression c = System.Linq.Expressions.Expression.Constant(value, value.GetType());
BinaryExpression b = null;
switch (d.Operator)
{
case FilterOperator.IsEqualTo:
b = System.Linq.Expressions.Expression.Equal(expression, c);
break;
case FilterOperator.Contains:
//b = GetExpression<T>(expression.ToString(), value.ToString()).Body as BinaryExpression;
break;
case FilterOperator.IsGreaterThanOrEqualTo:
b = System.Linq.Expressions.Expression.GreaterThanOrEqual(expression, c);
break;
case FilterOperator.IsLessThanOrEqualTo:
b = System.Linq.Expressions.Expression.LessThanOrEqual(expression, c);
break;
}
CriteriaCollection.Add(b);
}
public Expression<Func<T, bool>> GetLambdaExpression()
{
ParameterExpression e = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
var orderedList = CriteriaCollection.OrderBy(cr => cr.Left.ToString()).ToList();
var disctinctValues = CriteriaCollection.Distinct(new BinaryExpressionComparer()).ToList();
List<BinaryExpression> orElseExpressionList = new List<BinaryExpression>();
foreach (var value in disctinctValues)
{
System.Linq.Expressions.BinaryExpression expression = null;
foreach (var criteria in orderedList.Where(cr => cr.Left.ToString().Equals(value.Left.ToString())))
{
if (expression == null)
{
expression = criteria;
}
else
{
if (expression.Left.ToString() == criteria.Left.ToString())
expression = System.Linq.Expressions.BinaryExpression.OrElse(expression, criteria);
else
expression = System.Linq.Expressions.BinaryExpression.AndAlso(expression, criteria);
}
}
orElseExpressionList.Add(expression);
}
System.Linq.Expressions.BinaryExpression expressionAnd = null;
foreach (var ex in orElseExpressionList)
{
if (expressionAnd == null)
{
expressionAnd = ex;
}
else
{
expressionAnd = System.Linq.Expressions.BinaryExpression.AndAlso(expressionAnd, ex);
}
}
if (expressionAnd != null)
{
return System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(expressionAnd, e);
}
else
{
return NoFilterExpression;
}
}
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return BinaryExpression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
private static System.Linq.Expressions.BinaryExpression Like(Expression lhs, Expression rhs)
{
//typeof(string).GetMethod("Contains", new Type[] { typeof(string) }, null);
Expression expression = Expression.Call(
typeof(FileInfoHelper).GetMethod("Like",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
, lhs, rhs);
return expression as BinaryExpression;
}
class BinaryExpressionComparer : IEqualityComparer<BinaryExpression>
{
#region IEqualityComparer<Contact> Members
public bool Equals(BinaryExpression x, BinaryExpression y)
{
return x.Left.ToString().Equals(y.Left.ToString());
}
public int GetHashCode(BinaryExpression obj)
{
return obj.Left.ToString().GetHashCode();
}
#endregion
}
}

How do I pass int array to RouteValueDictionary

I need generate this url: http://localhost:3178/Reports/?GroupId=1211&GroupId=1237
I'm trying:
var routeData = new RouteValueDictionary();
routeData.Add("GroupId", "1, 2");
getting: GroupId=1,%202
or
routeData.Add("GroupId", "1");
routeData.Add("GroupId", "2");
getting: An item with the same key has already been added
and even
routeData.Add("GroupId[0]", "1");
routeData.Add("GroupId[1]", "2");
getting: ?GroupId%5B0%5D=1&GroupId%5B1%5D=2
it's possible to somehow fix my issue?
The RouteValueDictionary is meant to provide information to routes. As such, I don't think it has the capability you're asking for. I've been using a custom helper to populate query string data based on what I pass into it:
public static string BuildPath(RequestContext context, string routeName, RouteValueDictionary routeValues, object additionalParams)
{
var vpd = RouteTable.Routes[routeName].GetVirtualPath(context, routeValues);
if (vpd == null)
return string.Empty;
var virtualpath = vpd.VirtualPath;
var addparams = BuildAdditionalParams(additionalParams);
if (!virtualpath.Contains("?") && addparams.Length > 0)
virtualpath = virtualpath + "?" + addparams;
else if (virtualpath.Contains("?") && addparams.Length > 0)
virtualpath = virtualpath + "&" + addparams;
return "/" + virtualpath;
}
protected static string BuildAdditionalParams(object additionalParams)
{
if (additionalParams == null)
return string.Empty;
StringBuilder sb = new StringBuilder();
Type type = additionalParams.GetType();
PropertyInfo[] props = type.GetProperties();
Action<string, string> addProperty = (name, value) =>
{
if (sb.Length > 0)
sb.Append("&");
sb.Append(name);
sb.Append("=");
sb.Append(value);
};
foreach (PropertyInfo prop in props)
{
var simplevalue = prop.GetValue(additionalParams, null);
if (simplevalue != null)
{
Type propertyType = prop.PropertyType;
if (Nullable.GetUnderlyingType(propertyType) != null)
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
if (propertyType.IsEnum)
{
addProperty(prop.Name, ((int)simplevalue).ToString());
}
else if (propertyType.IsArray && propertyType != typeof(string))
{
foreach (var val in prop.GetValue(additionalParams, null) as IEnumerable)
addProperty(prop.Name, val.ToString());
}
else
{
if (!string.IsNullOrEmpty(simplevalue.ToString()))
addProperty(prop.Name, simplevalue.ToString());
}
}
}
return sb.ToString();
}
This function will build a full path to your route and append the values in the additionalParams object as query string data. This can handle arrays, enums, nullable values, and other types that by executing their ToString method.
Hope this helps!

Resources