Showing posts with label Expression trees. Show all posts
Showing posts with label Expression trees. Show all posts

Thursday, May 18, 2023

Generate action urls from C# lambda expressions in ASP.Net MVC Core: good news for those who missed it there

In ASP.Net MVC (on top on .Net Framework) there was useful mechanism for generating actions urls from C# lambda expressions. Let's say we have controller UserController for managing users which has List action with 3 params:

  • page number (in case if users list is large and we need to use pagination)
  • sort by (first name, last name, etc)
  • sort direction (asc or desc)
public enum SortDirection
{
    Asc,
    Desc
}

public class UserController : Controller
{
    public ActionResult List(int pageNumber, string sortBy, SortDirection sortDirection)
    {
        ...
    }
}

In this case we could generate url for this action using generic ActionLink<T> method like this:

Html.ActionLink<UserController>(c => c.List(0, "FirstName", SortDirection.Asc), "All users")

which will generate the following url:

/user/list?pageNumber=0&sortBy=FirstName&sortDirection=SortDirection.Asc

That is convenient method since we have compile-time check for the code. Compare it with classic way:

Html.ActionLink("All users", "List", "Users", new { pageNumber = 0, sortBy = "FirstName", sortDirection = SortDirection.Asc })

In the last case if e.g. action name, parameters names or number of params will be changed we won't get any errors or warning during compilation. Instead we may get unexpected behavior or runtime error (e.g. if new parameter was added it will have default value if we won't explicitly add it to link generation code).

The problem is that strongly-typed method is not available in ASP.Net Core. Don't know what was the reason for not adding it there (except mentioned advantage of having compile-time check it may also be a problem during migration of old ASP.Net MVC app to ASP.Net Core) but fortunately it is possible to get it back there.

Lets check steps which are needed for generating action url from expression: we need to get controller name (/user), action name (/list), list action parameters names (or get route values from expression how it is called in ASP.Net MVC) and (most tricky one) get value of each parameter passed to expression. And then concatenate all parts to one string. First 3 steps are relatively easy: they can be done by basic reflection. But last step (get values of parameters passed to lambda expression) needs extra attention. In the past I already faced with that need in Camlex.NET (open source library for Sharepoint developers which I maintain in free time): Runtime evaluation of lambda expressions. We can use the same technique here as well. Also (as I found out during experiments) code for generating routing values from expression (list parameters names) can be reused with small changes from internal method of ASP.Net MVC Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression in ASP.NET Core - it will help to save our time.

Now if we will combine all of this we may create helper class for generating urls from expressions in ASP.Net Core:

public static class HtmlHelperExtensions
{
    public static string ActionLink<TController>(this IHtmlHelper html, Expression<Action<TController>> actionExpression)
    {
        return ActionLink(actionExpression, GetRouteValuesFromExpression(actionExpression));
    }

    public static string ActionLink<TController>(this IHtmlHelper html, Expression<Action<TController>> actionExpression, RouteValueDictionary routeValues)
    {
        return ActionLink(actionExpression, routeValues);
    }

    public static string ActionLink<TController>(Expression<Action<TController>> actionExpression, RouteValueDictionary routeValues)
    {
        string controllerName = typeof(TController).GetControllerName();
        string actionName = actionExpression.GetActionName();
        var sb = new StringBuilder($"/{controllerName}/{actionName}");
        if (routeValues != null)
        {
            bool isFirst = true;
            foreach (var routeValue in routeValues)
            {
                sb.Append(isFirst ? "?" : "&");
                sb.Append($"{routeValue.Key}={routeValue.Value}");
                isFirst = false;
            }
        }
        return sb.ToString();
    }

    private static string GetControllerName(this Type controllerType)
    {
        return controllerType.Name.Replace("Controller", string.Empty);
    }

    private static string GetActionName(this LambdaExpression actionExpression)
    {
        return ((MethodCallExpression)actionExpression.Body).Method.Name;
    }

    // copy of Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression
    private static RouteValueDictionary GetRouteValuesFromExpression<TController>(
        Expression<Action<TController>> action)
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));
        if (!(action.Body is MethodCallExpression body))
            throw new ArgumentException("MustBeMethodCall");
        string name = typeof(TController).Name;
        string str = name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
            ? name.Substring(0, name.Length - "Controller".Length)
            : throw new ArgumentException("TargetMustEndInController");
        if (str.Length == 0)
            throw new ArgumentException("CannotRouteToController");
        string targetActionName = GetTargetActionName(body.Method);
        var rvd = new RouteValueDictionary();
        AddParameterValuesFromExpressionToDictionary(rvd, body);
        return rvd;
    }

    private static void AddParameterValuesFromExpressionToDictionary(
        RouteValueDictionary rvd,
        MethodCallExpression call)
    {
        ParameterInfo[] parameters = call.Method.GetParameters();
        if (parameters.Length <= 0)
            return;
        for (int index = 0; index < parameters.Length; ++index)
        {
            Expression expression = call.Arguments[index];
            object obj = !(expression is ConstantExpression constantExpression)
                ? Expression.Lambda<Func<object, object>>((Expression)Expression.Convert(expression, typeof(object)), Expression.Parameter(typeof(object), "_unused")).Compile().Invoke((object)null)
                : constantExpression.Value;
            rvd.Add(parameters[index].Name, obj);
        }
    }

    private static string GetTargetActionName(MethodInfo methodInfo)
    {
        string name = methodInfo.Name;
        ActionNameAttribute actionNameAttribute = !methodInfo.IsDefined(typeof(NonActionAttribute), true)
            ? methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true).OfType<ActionNameAttribute>().FirstOrDefault<ActionNameAttribute>()
            : throw new InvalidOperationException("CannotCallNonAction");
        if (actionNameAttribute != null)
            return actionNameAttribute.Name;
        return name;
    }
}

Using this helper class we may again generate actions links from C# expressions in ASP.Net Core.


Tuesday, December 8, 2015

Speaking with lecture “Using .Net expression trees for creating translators on C#” in Computer Science Center in St-Petersburg

Yesterday I presented open lecture “Using .Net expression trees for creating translators on C#” in Computer Science Center in St-Petersburg: http://open.compscicenter.ru/. Computer Science Center – is initiative of leading IT companies in St-Petersburg which is intended to prepare students to work in IT industry by providing advanced education in computer science (programming languages, algorithms, etc.). It was interesting format and interesting experiences, thanks to all participants. Presentation from the lecture is available on SlideShare: http://www.slideshare.net/sadomovalex/net-c-55910243.

Saturday, February 4, 2012

Camlex.Net 3.1: support of dynamic ViewFields

On this week new version 3.1 of Camlex.Net was released. In this release we added support of the dynamic ViewFields. This feature was requested in one of the discussions on the codeplex Camlex site. I.e. the following new methods were added to the IQueryEx interface:

   1: public interface IQueryEx : IQuery
   2: {
   3:     ...
   4:     string ViewFields(IEnumerable<string> titles);
   5:     string ViewFields(IEnumerable<string> titles, bool includeViewFieldsTag);
   6:     string ViewFields(IEnumerable<Guid> ids);
   7:     string ViewFields(IEnumerable<Guid> ids, bool includeViewFieldsTag);
   8: }

I.e. now you can create list of field names or field ids dynamically and pass it into the Camlex. It will create CAML for ViewFields based on this list. For example the following code:

   1: var items = new [] { "Title", "FileRef" };
   2:  
   3: string caml = Camlex.Query().ViewFields(items);

will produce the following CAML:

   1: <FieldRef Name=\"Title\" />
   2: <FieldRef Name=\"FileRef\" />

I would like to share also some interesting (from my point of view :) ) implementation details. You can easily skip the rest of the post if you are not interested in it. Before version 3.1 there were the following methods (they still exist of course):

   1: public interface IQueryEx : IQuery
   2: {
   3:     string ViewFields(Expression<Func<SPListItem, object>> expr);
   4:     string ViewFields(Expression<Func<SPListItem, object>> expr, bool includeViewFieldsTag);
   5:     string ViewFields(Expression<Func<SPListItem, object[]>> expr);
   6:     string ViewFields(Expression<Func<SPListItem, object[]>> expr, bool includeViewFieldsTag);
   7: }

With these methods we were able to create ViewFields like this:

   1: string caml =
   2:     Camlex.Query().ViewFields(x => new [] { x["Title"], x["FileRef"] });

When add new functionality I wanted to reuse these existing methods in order to avoid creating of extra code. In order to do this the following question should be solved: having array of strings or guids (e.g. { “1”, “2”, “3” }) how to create lambda expression “x => new [] { x[“1”], x[“2”], x[“3”] }”? The solution is quite elegant:

   1: public string ViewFields(IEnumerable<string> titles, bool includeViewFieldsTag)
   2: {
   3:     ...
   4:     return this.ViewFields(this.createExpressionFromArray(titles), includeViewFieldsTag);
   5: }
   6:  
   7: public string ViewFields(IEnumerable<Guid> ids, bool includeViewFieldsTag)
   8: {
   9:     ...
  10:     return this.ViewFields(this.createExpressionFromArray(ids), includeViewFieldsTag);
  11: }
  12:  
  13: private Expression<Func<SPListItem, object[]>>
  14: createExpressionFromArray<T>(IEnumerable<T> items)
  15: {
  16:     return Expression.Lambda<Func<SPListItem, object[]>>(
  17:         Expression.NewArrayInit(typeof(object),
  18:         (IEnumerable<Expression>)items.Select(
  19:             t => Expression.Call(Expression.Parameter(typeof(SPListItem), "x"),
  20:                 typeof(SPListItem).GetMethod("get_Item", new[] { typeof(T) }),
  21:                 new[] { Expression.Constant(t) })).ToArray()),
  22:         Expression.Parameter(typeof(SPListItem), "x"));
  23: }

I.e. we iterate through all items in the list and for each item create expression – access to indexer x[item], and add them to the NewArrayInit expression. Note on the ToArray() call – without it lazy LINQ expression won’t be populated and array initialization will fail.

We are very interested in your feedback. Feel free to contact us via Camlex site on the codeplex.

Friday, November 11, 2011

Camlex.NET became bidirectional and goes online. Version 3.0 is released

Today 11.11.2011 (nice date) I have several good news. I’m happy to announce that several months of work which we made for new version of Camlex is finished. Today we released Camlex 3.0 (available on the project’s site: http://camlex.codeplex.com) with reverse engineering feature. Also we started new online service based on Camlex: Camlex Online (see below).

What actually “reverse engineering” means? “Basic” Camlex.NET allows you to write CAML queries using C# code like this:

   1: string caml = Camlex.Query()
   2:         .Where(x => (string)x["Title"] == "Hello world!").ToString();

which produce the following string:

   1: <Where>
   2:   <Eq>
   3:     <FieldRef Name="Title" />
   4:     <Value Type="Text">Hello world!</Value>
   5:   </Eq>
   6: </Where>

In addition to possibility to build dynamic queries (using WhereAll(), WhereAny() methods and ExpressionHelper class) and combine expressions using logical operations (And Or) it became useful tool for Sharepoint developers and we got a many positive comments for it (thanks for everybody who posted ideas and suggestions for future development features).

With version 3.0 we reversed Camlex and taught it to work in opposite direction: from CAML to C#. It will allow to easily refactor existing code which uses hardcoded literals for CAML to the Camlex.NET with compile-time checks and code-first. In order to simplify usage for developers – we ordered domain name and created online service where you can perform this conversion: Camlex Online.

So you may put CAML into the textbox:

   1: <Query>
   2:   <Where>
   3:     <Eq>
   4:       <FieldRef Name="Title" />
   5:       <Value Type="Text">Hello world!</Value>
   6:     </Eq>
   7:   </Where>
   8: </Query>

and click Convert to C#. It will produce the following code:

   1: Camlex.Query().Where(x => (string)x["Title"] == "Hello world!")

This is not the only change in 3.0 version. We extended object model so it will allow to make many other interesting conversions in future, but this is a topic of another posts.

Several words about how it works. Here is the architecture of the basic Camlex (details are here):

image

Schema of the reverse engineering is a little bit more complicated:

image

Re prefix here means Reverse Engineering. These ReTranslator, ReAnalyzer, ReLinker are completely different from Translator and Analyzer from basic Camlex. For reverse engineering we actually implemented 2nd Camlex (if we take into account amount of new code and ~500 unit tests :) ).

As you can see in output we have expression tree. In order to convert it to C# we used another open source project ExpressionToCode where we have to fix several bugs in order to adopt it for all our cases (some are reported here, but not all. Several workarounds which are specific only for Camlex were not posted). So last step is transformation from expression tree to C#:

image

Starting from version 3.0 we will implement all new features with 2 directions CAML <-> C#, so it will retain reversible. Of course reverse engineering is not complete mirror. E.g. it works only with constant CAML string, while basic Camlex supports also non-constant values (I think the reason is quite obvious). Plus it doesn’t currently supports combining of multiple And/Or operations via WhereAll/WhereAny, i.e. they will be always translated as multiple && and ||. However it is possible to add it in future.

During development of reverse engineering basic Camlex was not frozen. I created separate branch for Re version while basic CAML remain in default (fortunately after I moved it from svn to mercurial it became much easy to merge branches). When Re version was almost ready we merged default branch into re, implement reverse of the features which were added into default and merge it back to default.

As I said this release opened new opportunities for further development. Also we have plenty of feature requests which were pending until re will be finished. So we will continue our work to make Camlex better and try to simplify hard life of Sharepoint developers.

Also I would like to thanks Vladimir Timashkov who found time to help finish the work. Thanks for creators of ExpressionToCode – it greatly helped us. And as always we are open for your ideas and suggestions. Feel free to post them on the Codeplex site.

Saturday, October 29, 2011

“No coercion operator is defined between types” error in expression trees

Suppose that you have 2 inherited classes:

   1: public class Bar
   2: {
   3:     public static explicit operator Bar(string s) { return null; }
   4: }
   5: public class Foo : Bar
   6: {
   7: }

As shown above in the parent class Boo there is explicit cast operator from string (the fact that it returns null is not relevant for us). With this operator you may cast Bar from string:

   1: Bar b = (Bar)"foo";

Also you may cast to Foo and compiler will allow to do that:

   1: Foo f = (Foo)"foo";

Now let’s try to construct expression tree which converts string to Foo:

   1: var expr = Expression.Convert(Expression.Constant("foo"), typeof (Foo));

It will compile but will throw exception “No coercion operator is defined between types 'System.String' and 'Foo'”. This looks strange because compiler allows to do the same cast directly.

Ok, there is overridden version of Convert method and if you checked my previous post about similar problem with LessThan method, we can try to use it in order to avoid the problem:

   1: var mi = typeof(Bar).GetMethod("op_Explicit");
   2: var expr = Expression.Convert(Expression.Constant("foo"), typeof(Foo), mi);

Unfortunately no luck here as well: it throws exception “The operands for operator 'Convert' do not match the parameters of method 'op_Explicit'”.

If you will try to get op_Explicit using Foo type:

   1: var mi = typeof (Foo).GetMethod("op_Explicit");

you will again get “No coercion operator is defined between types 'System.String' and 'Foo'”” error. The same behavior exist both in .Net 3.5 and 4.0. This problem looks like a bug in expression trees.

Tuesday, October 11, 2011

Fixing error “The binary operator LessThan is not defined for the types 'Type1' and 'Type2'”

During the working over next version of Camlex.NET library we encountered with the interesting problem: when we tried to construct LessThan expression for our types the following exception was thrown:

The binary operator LessThan is not defined for the types 'System.Object' and 'SomeType'.

SomeType here is the inheritor of the class where operation “<” is defined. But at the same time there is no explicitly defined operator “<” in SomeType it self. Nevertheless it allows you to write the following correct C# code:

   1: class BaseType
   2: {
   3:     public static bool operator <(object c1, BaseType c2) { return false; }
   4:     public static bool operator >(object c1, BaseType c2) { return false; }
   5: }
   6:  
   7: class SomeType : BaseType
   8: {
   9: }
  10:  
  11: ...
  12:  
  13: var o = new object();
  14: var c = new DataTypes.Text();
  15: bool b = o < c;

This code is successfully compiled and executed, i.e. operator “<” is called for operands of object and SomeType types. However if you will try to construct LessThan expression with left operand of object type and right operand of SomeType type (using Expression.LessThan method), you will get the mentioned exception. If you will use right operand of type BaseType, expression will be constructed correctly. Also you can define operators “<” and “>” directly in SomeType (and use SomeType as 2nd parameter) – expression will also be created. Seems like Expression.LessThan doesn’t search for operators defined in base classes. Instead it searches only in specified class itself (thanks to Vladimir who found the problem).

However there is more elegant solution for the problem which doesn’t require changing of the code. It was crucial for us because as I said we faced with this issue when working on Camlex where we needed to construct expressions like this:

   1: x => x["Foo"] > (DataTypes.Integer)123

x has SPListItem type which has several indexers, one of them receives string as parameter and returns object. Here is more realistic test:

   1: public class BaseFieldType
   2: {
   3:     public static explicit operator BaseFieldType(string s) { return null; }
   4: }
   5:  
   6: public class BaseFieldTypeWithOperators : BaseFieldType
   7: {
   8:     public static bool operator <(object c1, BaseFieldTypeWithOperators c2) { return false; }
   9:     public static bool operator >(object c1, BaseFieldTypeWithOperators c2) { return false; }
  10: }
  11:  
  12: public class StringBasedFieldType : BaseFieldTypeWithOperators
  13: {
  14:     public bool Contains(string text) { return true; }
  15:     public bool StartsWith(string text) { return true; }
  16: }
  17:  
  18: public static class DataTypes
  19: {
  20:     public class Text : StringBasedFieldType
  21:     {
  22:     }
  23: }
  24:  
  25: public class SPListItem
  26: {
  27:     public object this[string s] { get { return null; } }
  28: }
  29:  
  30: class Program
  31: {
  32:     static void Main(string[] args)
  33:     {
  34:         var mi = typeof(SPListItem).GetProperty("Item",
  35:             typeof(object), new[] { typeof(string) }, null).GetGetMethod();
  36:         var left =
  37:                 Expression.Call(
  38:                     Expression.Parameter(typeof(SPListItem), "x"),
  39:                     mi, new[] { Expression.Constant("foo") });
  40:  
  41:  
  42:         var right = Expression.Convert(Expression.Convert(Expression.Constant("foo"),
  43:             typeof(BaseFieldType)), typeof(DataTypes.Text));
  44:  
  45:         var expr = Expression.LessThan(left, right);
  46:     }
  47: }

This code fails because on left operand passed on the line 48 has type object, and right operand has type DataTypes.Text which is subclass of BaseFieldTypeWithOperators and which doesn't has explicitly defined operators in it.

Solution is quite simple: you should use overridden version of Expression.LessThan method which receives 4 parameters:

   1: BinaryExpression LessThan(Expression left, Expression right, bool liftToNull, MethodInfo method)

In last parameter you should specify MethodInfo variable which represents operator “<” from the base class. I.e. the previous example will look like this:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var mi = typeof(SPListItem).GetProperty("Item",
   6:             typeof(object), new[] { typeof(string) }, null).GetGetMethod();
   7:         var left =
   8:                 Expression.Call(
   9:                     Expression.Parameter(typeof(SPListItem), "x"),
  10:                     mi, new[] { Expression.Constant("foo") });
  11:  
  12:  
  13:         var right = Expression.Convert(Expression.Convert(Expression.Constant("foo"),
  14:             typeof(BaseFieldType)), typeof(DataTypes.Text));
  15:  
  16:  
  17:         var info = typeof(BaseFieldTypeWithOperators).GetMethod("op_LessThan");
  18:  
  19:         var expr = Expression.LessThan(left, right, false, info);
  20:     }
  21: }

Difference is in lines 17-19: at first we get reference to operator “<” (op_LessThan) and after that we pass it into Expression.LessThan() method. After this expression will be successfully created.