Posts tagged with c

Creating OR expressions in LINQ

As everybody who has read my blog before knows, I love LINQ and miss it when coding in other languages, so it’s nice when I get a chance to use it again. When I come back to it with fresh eyes, I notice some things aren’t as easy as they should be - and this time is no exception.

Background

People often need to build up LINQ expressions at runtime based on filters or criteria a user has selected. Adding criteria is incredibly easy, as you can chain operations together on the IQueryable interface, e.g.

if (customerActive)
  query = query.Where(c => c.IsActive);
if (customerCountry != null)
  query = query.Where(c => c.Country == customerCountry);

This is great if you want to AND things together, which is often the case in building up filters. Other operations, like Contains, are useful in allowing many options against a specific field.

Problem

But what about offering a choice involving either of two properties? One way to write it would be:

if (customerActive) {
  if (customerEnterprise) {
    query = query.Where(c => c.IsActive || c.IsEnterprise);
  } else {
    query = query.Where(c => c.IsActive);
  } else {
    if (customerEnterprise) {
      query = query.Where(c => c.IsEnterprise);
    }
  }
}

This code is already hard to read and is going to increase in complexity for each new option.

Solution

With some helper code, we can combine two expression predicates into a single OrElse for use in a Where condition.

class Or {
  public static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) {
    if (left == null && right == null) throw new ArgumentException("At least one argument must not be null");
    if (left == null) return right;
    if (right == null) return left;
    
    var parameter = Expression.Parameter(typeof(T), "p");
    var combined = new ParameterReplacer(parameter).Visit(Expression.OrElse(left.Body, right.Body));
    return Expression.Lambda<Func<T, bool>>(combined, parameter);
  }

  class ParameterReplacer : ExpressionVisitor {
    readonly ParameterExpression parameter;

    internal ParameterReplacer(ParameterExpression parameter) {
      this.parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression node) {
      return parameter;
    }
  }
}

Now we can:

Expression<Func<Customer, bool>> criteria = null;
if (customerActive)
    criteria = Or.Combine(criteria, c => c.IsActive == customerActive);
if (customerEnterprise)
    criteria = Or.Combine(criteria, c => c.IsEnterprise == customerEnterprise);

How it works

There are two parts to this working. The first combines the two separate predicates into a single OR lambda, taken care of by the Expression.OrElse method.

That alone can’t quite do what we need because the two expressions each have a separate parameter variable. The visitor replaces both of these with a new parameter we’ll pass into the new Lambda expression we’re creating that combines both conditions.

Caveats

The code, as it stands, creates an unbalanced tree. Ideally, the tree should be balanced to limit the depth LINQ providers must traverse to translate the query. I doubt you’ll run into the dreaded Stack Overflow (the message, not the site), but if you do, that’s why.

An overload that takes an Enumerable of Expression<Func<T, bool>> and produces a balanced tree is left as an exercise to the reader :p

[)amien

Azure Pipeline Build Variables

Azure Pipelines has been interesting to me especially given the generous free open source tier and seemingly instant availability of build agents. The setup is easy if you’re building .NET targets with lots of useful starters available.

Some areas have been frustrating, specifically build variables, the difficulty in getting the app installed, and the limitations on triggers if you can’t (non-GitHub apps can’t be installed on Enterprise accounts even if FREE).

We’ll concentrate on the build variable oddities for this post.

The Pipeline configuration helpfully has a tab named Variables that looks much like you’d expect with a big list of names, values, a lock icon and a checkbox for ‘Settable at queue time’.

There are, however, some oddities when compared to existing CI servers with these - especially if you expect to be able to use them as environment variables.

Naming

The documentation for these - especially for Predefined build variables shows the expected variables but, they use a dot naming convention.

This dot naming convention does not work if you are using them as environment variables. They are upper-cased and . is replaced with _ so for example:

Build.BuildNumber is actually BUILD_BUILDNUMBER.

Secrets

One even more confusing element is that when you mark your own (sensibly named) value as secret it suddenly stops being available as an environment variable.

Note: You should never build PRs with secret variables. Doing so allows anyone to submit a PR that simply echos them to the build log for harvesting. By default secrets are not available to pull requests in Azure Pipelines - leave it that way!

So how do you get these secret variables re-exposed as environment variables? While Stack Overflow has a bunch of complicated answers there is a much simpler way:

  1. Select the step from your job
  2. Expand the environment variables section
  3. Repeat the following steps for each environment variable
  4. Enter your variable name in the Name box
  5. Enter $(variable_name) in the Value box

Now when you run the job, the build step will use the secure variable. This screenshot shows you an example:

Screenshot of the Azure Pipeline Variable being mapped

If I find workarounds for the app installations, I’ll blog them!

[)amien

Comma-separated parameter values in WebAPI

The model binding mechanism in ASP.NET is pretty slick - it’s highly extensible and built on TypeDescriptor for re-use that lets you avoid writing boilerplate code to map between CLR objects and their web representations.

One surprise, however, is that out of the box, neither WebAPI nor MVC support comma-separated parameter values when bound to an array, e.g.

public class MyController : Controller {
    public string Page([FromUri]int[] ids) {
        return String.Join(" ; ", ids);
    }
}

Will only return 1 ; 2 ; 3 when supplied with /my/page?ids=1&ids=2&ids=3 and if you instead give it /my/page?ids=1,2,3 it will fail.

The reason was likely because there is no standard for this at all and that the former - supported - scenario maps to what forms do when they post multiple value selections such as that in a select list box. The latter is much more readable and is expected by some client frameworks and supported by some other web frameworks such as the Java Spring MVC framework.

Of course, that extensible system lets us easily extend this behaviour. We can support both transparently - and interestingly enough - even mix-and-match on the same URL. So for example;

/my/page?ids=1,2&ids=3 will now return 1 ; 2 ; 3 in our example.

Although this supports both types if you are currently using commas in your number format this would break your app. e.g. ?ids=1,200&ids=3,500 would have been correctly received as 1200, 500 but now would be incorrectly received as 1, 200, 3, 500

CommaSeparatedArrayModelBinder class

My DamienGKit project contains the source but, I’ll also present it here.

Out of the box, this supports integer types and GUIDs and could be extended for floats and decimals – just be careful with that formatting!

public class CommaSeparatedArrayModelBinder : IModelBinder {
    private static readonly Type[] supportedElementTypes = {
        typeof(int), typeof(long), typeof(short), typeof(byte),
        typeof(uint), typeof(ulong), typeof(ushort), typeof(Guid)
    };

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
        if (!IsSupportedModelType(bindingContext.ModelType)) return false;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        var stringArray = valueProviderResult?.AttemptedValue
            ?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
        if (stringArray == null) return false;
        var elementType = bindingContext.ModelType.GetElementType();
        if (elementType == null) return false;

        bindingContext.Model = CopyAndConvertArray(stringArray, elementType);
        return true;
    }

    private static Array CopyAndConvertArray(IReadOnlyList<string> sourceArray, Type elementType) {
        var targetArray = Array.CreateInstance(elementType, sourceArray.Count);
        if (sourceArray.Count > 0) {
            var converter = TypeDescriptor.GetConverter(elementType);
            for (var i = 0; i < sourceArray.Count; i++)
                targetArray.SetValue(converter.ConvertFromString(sourceArray[i]), i);
        }
        return targetArray;
    }

    internal static bool IsSupportedModelType(Type modelType) {
        return modelType.IsArray && modelType.GetArrayRank() == 1
                && modelType.HasElementType
                && supportedElementTypes.Contains(modelType.GetElementType());
    }

}

public class CommaSeparatedArrayModelBinderProvider : ModelBinderProvider {
    public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) {
        return CommaSeparatedArrayModelBinder.IsSupportedModelType(modelType)
            ? new CommaSeparatedArrayModelBinder() : null;
    }
}

To register

It’s necessary to register ModelBinderProviders with your ASP.NET application at start-up - usually in the WebApiConfig.cs file.

public static class WebApiConfig {
    public static void Register(HttpConfiguration config) {
        // All your usual configuration up here
        config.Services.Insert(typeof(ModelBinderProvider), 0, new CommaSeparatedArrayModelBinderProvider());
    }
}

[)amien

Model binding form posts to immutable objects

I’ve been working on porting over my blog to a static site generator. I fired up an Azure Function to handle the form-comment to PR process to enable user comments to still be part of the site without using a 3rd party commenting system - more on that in the next post - and found the ASP.NET model binding for form posts distinctly lacking.

It’s been great getting back into .NET and brushing up some skills making the code clear, short and reusable. What I wanted was a super-clear action on my controller that tried to collect, validate and sanitize the data then, if all was well, create the pull request or report errors.

Ideally, it would look like this;

[FunctionName("PostComment")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestMessage request) {
    var form = await request.Content.ReadAsFormDataAsync();
    if (TryCreateComment(form, out Comment comment, out var errors))
        await CreateCommentAsPullRequest(comment);
    return request.CreateResponse(errors.Any()
      ? HttpStatusCode.BadRequest : HttpStatusCode.OK, String.Join("\n", errors));
}

To do that, we need a function capable of creating the Comment class from the form post. You could manually do it field by field, but that’s not reusable, highly repetitive, and, of course, no fun. The Comment class is - like all well-behaved little objects - immutable.

Creating a function to do this is simple with a little bit of reflection;

private static object ConvertParameter(string parameter, Type targetType) {
    return String.IsNullOrWhiteSpace(parameter)
           ? null : TypeDescriptor.GetConverter(targetType).ConvertFrom(parameter);
}

private static bool TryCreateCommentFromForm(NameValueCollection form, out Comment comment, out List<string> errors) {
    var constructor = typeof(Comment).GetConstructors()[0];
    var values = constructor.GetParameters()
                            .ToDictionary(p => p.Name, p => ConvertParameter(form[p.Name], p.ParameterType)
                                      ?? (p.HasDefaultValue ? p.DefaultValue : new MissingRequiredValue()));
    errors = values.Where(p => p.Value is MissingRequiredValue)
                   .Select(p => $"Form value missing for '{p.Key}'").ToList();
    comment = errors.Any() ? null : (Comment)constructor.Invoke(values.Values.ToArray());
    return !errors.Any();
}

This method grabs the constructor for the Comment and tries to find keys in the form that match the parameter name. Any missing are reported as errors unless they have a default value, in which case that default is used. MissingRequiredValue is just an empty object to act as a sentinel. The use of TypeDescriptor.GetConverter means it should be quite happy handling integers, decimals, and URLs.

The Comment constructor specifies which fields are required, and the parameter names must match the form field names by convention. Any optional value has a default value that the constructor provides a sensible default for.

public Comment(string post_id, string message, string author, string email,
    DateTime? date = null, Uri url = null, int? id = null, string gravatar = null) {
    this.post_id = pathValidChars.Replace(post_id, "-");
    this.message = message;
    this.author = author;
    this.email = email;
    this.date = date ?? DateTime.UtcNow;
    this.url = url;
    this.id = id ?? new { this.post_id, this.author, this.message, this.date }.GetHashCode();
    this.gravatar = gravatar ?? EncodeGravatar(email);
}

I’ll post more of the form commenting system source soon once it’s a bit better tested and I’ve looked into anti-spam integration. The Jekyll rendering templates and WordPress exporter are available.

[)amien