A co-worker was working on a new MVC project using role based authorization when he ran into a concern where a view was nearly identical between two different roles. The only difference between the views was one role had the ability to “Delete” and included a delete button, the other role didn’t have the delete capability.

One solution to the problem would have been to add the following code into the view:

if (User.IsInRole("Administrator")) {
  <button>delete</button>
}

Using the above wouldn’t be the end of the word, since the “Delete” action on the controller would be limited to only the “Administrator” role. However, in the future as the abilities of the two roles diverge, we’re likely to continue sprinkling more authorization code into our view creating a bundle of mud. I recommended the developer to create a dedicated view for each role, using partials where able to minimize code duplication.

By doing so, we can have the controller determine the correct view to render.

public class HomeController: Controller
{
  [AcceptVerbs(HttpVerbs.Get)]
  public virtual ActionResult Index()
  {
    if (User.IsInRole("Administrator")) {
        return (View("AdministratorIndex"));
    }
    return (View());
  }
}

That will work and is better than having the the authorization logic inside of the view. We could even make it better by moving the custom views into a subfolder based on the role to help keep our files organized.

- Views
  - Home
    - Administrator
      index.cshtml
    index.cshtml

That’ll help keep our role custom views organized.

But something still “smells” here. Our controller / action is now dealing with authorization concerns. Can we somehow move that authorization logic outside of our controller? Essentially our authorization logic is merely selecting what view to render. Let’s see if we can override the view selection logic…

ViewEngine (RazorViewEngine) To The Rescue

Looking at the ASP.NET MVC stack, we can see that there are ViewEngine classes, (RazorViewEngine, WebFormViewEngine) which will apply a set of rules to locating the appropriate view.

To implement this, we’ll create an RoleBasedRazorViewEngine class with the following contents:

/// <summary>
/// A razor based view engine that locates views based on their role.
/// </summary>
public class RoleBasedRazorViewEngine: RazorViewEngine
{
  private readonly IEnumerable<string> _roles;

  /// <summary>
  /// Creates an instance of the RoleBasedRazorViewEngine class.
  /// </summary>
  /// <param name="roles">The list of roles in priority order supported by the application.</param>
  public RoleBasedRazorViewEngine(IEnumerable<string> roles): this(roles, null)
  {
  }

  /// <summary>
  /// Creates an instance of the RoleBasedRazorViewEngine class.
  /// </summary>
  /// <param name="roles">The list of roles in priority order supported by the application.</param>
  /// <param name="viewPageActivator">The ViewPageActivator to use for page dependency resolution.</param>
  public RoleBasedRazorViewEngine(IEnumerable<string> roles, IViewPageActivator viewPageActivator): base(viewPageActivator)
  {
    _roles = roles ?? new String[0];

    AreaViewLocationFormats = new [] {
      "~/Areas/{2}/Views/{1}/{{0}}/{0}.cshtml",
      "~/Areas/{2}/Views/{1}/{{0}}/{0}.vbhtml",
      "~/Areas/{2}/Views/Shared/{{0}}/{0}.cshtml",
      "~/Areas/{2}/Views/Shared/{{0}}/{0}.vbhtml"
    }.Concat(base.AreaViewLocationFormats).ToArray();
    AreaMasterLocationFormats = new[] {
      "~/Areas/{2}/Views/{1}/{{0}}/{0}.cshtml",
      "~/Areas/{2}/Views/{1}/{{0}}/{0}.vbhtml",
      "~/Areas/{2}/Views/Shared/{{0}}/{0}.cshtml",
      "~/Areas/{2}/Views/Shared/{{0}}/{0}.vbhtml"
    }.Concat(base.AreaMasterLocationFormats).ToArray();
    AreaPartialViewLocationFormats = new[] {
      "~/Areas/{2}/Views/{1}/{{0}}/{0}.cshtml",
      "~/Areas/{2}/Views/{1}/{{0}}/{0}.vbhtml",
      "~/Areas/{2}/Views/Shared/{{0}}/{0}.cshtml",
      "~/Areas/{2}/Views/Shared/{{0}}/{0}.vbhtml"
    }.Concat(base.AreaPartialViewLocationFormats).ToArray();

    ViewLocationFormats = new[] {
      "~/Views/{1}/{{0}}/{0}.cshtml",
      "~/Views/{1}/{{0}}/{0}.vbhtml",
      "~/Views/Shared/{{0}}/{0}.cshtml",
      "~/Views/Shared/{{0}}/{0}.vbhtml"
    }.Concat(base.ViewLocationFormats).ToArray();
    MasterLocationFormats = new[] {
      "~/Views/{1}/{{0}}/{0}.cshtml",
      "~/Views/{1}/{{0}}/{0}.vbhtml",
      "~/Views/Shared/{{0}}/{0}.cshtml",
      "~/Views/Shared/{{0}}/{0}.vbhtml"
    }.Concat(base.MasterLocationFormats).ToArray();
    PartialViewLocationFormats = new[] {
      "~/Views/{1}/{{0}}/{0}.cshtml",
      "~/Views/{1}/{{0}}/{0}.vbhtml",
      "~/Views/Shared/{{0}}/{0}.cshtml",
      "~/Views/Shared/{{0}}/{0}.vbhtml"
    }.Concat(base.PartialViewLocationFormats).ToArray();
  }

  /// <summary>
  /// Creates a partial view using the specified controller context and partial path.
  /// </summary>
  /// <returns>
  /// The partial view.
  /// </returns>
  /// <param name="controllerContext">The controller context.</param><param name="partialPath">The path to the partial view.</param>
  protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
  {
    return base.CreatePartialView(controllerContext, GetRoleBasedPath(controllerContext, partialPath));
  }

  /// <summary>
  /// Creates a view by using the specified controller context and the paths of the view and master view.
  /// </summary>
  /// <returns>
  /// The view.
  /// </returns>
  /// <param name="controllerContext">The controller context.</param>
  /// <param name="viewPath">The path to the view.</param>
  /// <param name="masterPath">The path to the master view.</param>
  protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
  {
    return base.CreateView(controllerContext, 
                           GetRoleBasedPath(controllerContext, viewPath), 
                           GetRoleBasedPath(controllerContext, masterPath));
  }

  /// <summary>
  /// Resolves the path based on the role information.
  /// </summary>
  /// <param name="controllerContext">The controller context.</param>
  /// <param name="viewPath">The path to the view.</param>
  /// <returns>The resolved view path.</returns>
  private string GetRoleBasedPath(ControllerContext controllerContext, string viewPath)
  {
    if ((! String.IsNullOrEmpty(viewPath)) && 
        (controllerContext.HttpContext.User != null)) {
      IPrincipal principal = controllerContext.HttpContext.User;
      foreach (string role in _roles.Where(role => principal.IsInRole(role))) {
        string resolvedViewPath = String.Format(CultureInfo.InvariantCulture, viewPath, role);
        if (base.FileExists(controllerContext, resolvedViewPath)) {
          return (resolvedViewPath);
        }
      }
    }
    return (viewPath);
  }

  /// <summary>
  /// Gets a value that indicates whether a file exists in the specified virtual file system (path).
  /// </summary>
  /// <returns>
  /// true if the file exists in the virtual file system; otherwise, false.
  /// </returns>
  /// <param name="controllerContext">The controller context.</param><param name="virtualPath">The virtual path.</param>
  protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
  {
    if (controllerContext.HttpContext.User != null) {
      IPrincipal principal = controllerContext.HttpContext.User;
      if (_roles.Where(role => principal.IsInRole(role))
                .Any(role => base.FileExists(controllerContext, String.Format(CultureInfo.InvariantCulture, virtualPath, role)))) {
        return (true);
      }
    }
    return(base.FileExists(controllerContext, virtualPath));
  }

  /// <summary>
  /// Finds the specified partial view by using the specified controller context.
  /// </summary>
  /// <returns>
  /// The partial view.
  /// </returns>
  /// <param name="controllerContext">The controller context.</param>
  /// <param name="partialViewName">The name of the partial view.</param>
  /// <param name="useCache">true to use the cached partial view.</param>
  /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext"/> parameter is null (Nothing in Visual Basic).</exception>
  /// <exception cref="T:System.ArgumentException">The <paramref name="partialViewName"/> parameter is null or empty.</exception>
  public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
  {
    return(base.FindPartialView(controllerContext, partialViewName, false));
  }

  /// <summary>
  /// Finds the specified view by using the specified controller context and master view name.
  /// </summary>
  /// <returns>
  /// The page view.
  /// </returns>
  /// <param name="controllerContext">The controller context.</param>
  /// <param name="viewName">The name of the view.</param>
  /// <param name="masterName">The name of the master view.</param>
  /// <param name="useCache">true to use the cached view.</param>
  /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext"/> parameter is null (Nothing in Visual Basic).</exception>
  /// <exception cref="T:System.ArgumentException">The <paramref name="viewName"/> parameter is null or empty.</exception>
  public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
  {
    return (base.FindView(controllerContext, viewName, masterName, false));
  }
}

A Walkthrough

The logic is pretty simple.

All of the available application roles are injected into the RoleBasedRazorViewEngine in priority order. Basically if someone is a member of multiple roles, the first role with a matching view will be returned.

We prepend custom views paths to include a file path based on roles. The defaults paths use the .NET string formatting place holders. The default .NET placeholders for MVC paths are:

  • {0} – The name of the action.
  • {1} – The name of the controller.
  • {2} – The name of the area.

When dealing with .NET place holder values, if you actually want to output the value {0} in a string that is being formatted, you wrap it with double braces like {{0}}. In our custom paths, we want the default .NET MVC string substitutions to occur. After that parsing has occurred, we’ll do a second string format / replacement specifying each of the user’s roles.

  ViewLocationFormats = new[] {
    "~/Views/{1}/{{0}}/{0}.cshtml",
    "~/Views/{1}/{{0}}/{0}.vbhtml",
    "~/Views/Shared/{{0}}/{0}.cshtml",
    "~/Views/Shared/{{0}}/{0}.vbhtml"
  }.Concat(base.ViewLocationFormats).ToArray();

Given the previous example, for the Index action on the Home controller, our custom search path of view locations would be:

  "~/Views/Home/{0}/index.cshtml",
  "~/Views/Home/{0}/index.vbhtml",
  "~/Views/Shared/{0}/index.cshtml",
  "~/Views/Shared/{0}/index.vbhtml",
  "~/Views/Home/index.cshtml",
  "~/Views/Home/index.vbhtml",
  "~/Views/Shared/index.cshtml",
  "~/Views/Shared/index.vbhtml"

Or our second pass, we’ll dynamically fill in what {0} should be with one of the user’s roles.

In the FileExists method, we check to see if the user belongs to any of the predefined roles. At this point, we don’t actually return the name of the file, we just indicate if we can resolve the requested path.

  protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
  {
    if (controllerContext.HttpContext.User != null) {
      IPrincipal principal = controllerContext.HttpContext.User;
      if (_roles.Where(role => principal.IsInRole(role))
                .Any(role => base.FileExists(controllerContext, String.Format(CultureInfo.InvariantCulture, virtualPath, role)))) {
        return (true);
      }
    }
    return(base.FileExists(controllerContext, virtualPath));
  }

If we had the following defined roles for our application: “Administrator”, “Operator”, “User”, the following file names would be searched in order.

  "~/Views/Home/Administrator/index.cshtml",
  "~/Views/Home/Administrator/index.vbhtml",
  "~/Views/Shared/Administrator/index.cshtml",
  "~/Views/Shared/Administrator/index.vbhtml",
  "~/Views/Home/Operator/index.cshtml",
  "~/Views/Home/Operator/index.vbhtml",
  "~/Views/Shared/Operator/index.cshtml",
  "~/Views/Shared/Operator/index.vbhtml",
  "~/Views/Home/User/index.cshtml",
  "~/Views/Home/User/index.vbhtml",
  "~/Views/Shared/User/index.cshtml",
  "~/Views/Shared/User/index.vbhtml",
  "~/Views/Home/index.cshtml",
  "~/Views/Home/index.vbhtml",
  "~/Views/Shared/index.cshtml",
  "~/Views/Shared/index.vbhtml"

If one of those paths match, the CreateView or CreatePartialView methods will dynamically expand the path using a helper function to locate the correct view based on the role.

  private string GetRoleBasedPath(ControllerContext controllerContext, string viewPath)
  {
    if ((! String.IsNullOrEmpty(viewPath)) && 
        (controllerContext.HttpContext.User != null)) {
      IPrincipal principal = controllerContext.HttpContext.User;
      foreach (string role in _roles.Where(role => principal.IsInRole(role))) {
        string resolvedViewPath = String.Format(CultureInfo.InvariantCulture, viewPath, role);
        if (base.FileExists(controllerContext, resolvedViewPath)) {
          return (resolvedViewPath);
        }
      }
    }
    return (viewPath);
  }

To use this new custom ViewEngine, we simply need to register it in our Global.asax.cs file.

    /// <summary>
    /// Occurs when the first resource is requested from the web server and the web application starts.
    /// </summary>
    protected void Application_Start()
    {
      ViewEngines.Engines.Clear();
      ViewEngines.Engines.Add(new RoleBasedRazorViewEngine(new[] { "Administrator", "Operator", "User" }));
    }

With the new view engine registered, we can go back and remove our authorization or view selection logic from our controller.

public class HomeController: Controller
{
  [AcceptVerbs(HttpVerbs.Get)]
  public virtual ActionResult Index()
  {
    return (View());
  }
}

Nice, simple, clean code with a separation of concerns! We let the view engine do it’s job and pick the right view for our controller action.

As everyone knows, mocking the HttpContext and associated classes is a nightmare and should just be avoided. I recently joined a different team at work where they were still running a lot of .NET 1.0 code. Most of the code was poorly designed and highly coupled having been written primarily by developers without proper object oriented design training. Calling this code “spaghetti code” would have been an insult to spaghetti code.

How bad? Most methods are over 1000 lines of code, filled with nested if/else statements and copy/pasted code all over. Extracting the business logic from one method and class resulted in 15 new classes. Here is an quick example of the code quality.

if (_username.ToLower().PadRight(12, ' ').Substring(0, 7).Equals("demo123")) {
}

Lots of useless string parsing and casting to wade through… But anyway, that isn’t the point of this post. Long story short is that as I’m modularizing this code I’ve run into the dreaded HttpContext.Current integrated throughout the code. Before I make extensive changes to the code, I wanted to have some unit tests to ensure that I wasn’t breaking the existing functionality as I modified the code. So the first thing I did was to inject the HttpContext as a dependency into the class. Although far from ideal, it allows me to at least run the code outside of IIS.

Here is my test helper to get the HttpContext:

/// <summary>
/// Retreives an HttpContext for testing.
/// </summary>
/// <returns>An HttpContext for testing.</returns>
internal HttpContext GetHttpContext(string url = "http://127.0.0.1/")
{
  var request = new HttpRequest(String.Empty, url, String.Empty);
  var response = new HttpResponse(new StringWriter());
  var context = new HttpContext(request, response);
  return(context);
}

Unfortunately, the code has numereous references to Request.ServerVariables. If you try to add to this NameValueCollection you’ll find that it is a read only collection. Here is the decompiled code:

public sealed class HttpRequest
{
  private HttpServerVarsCollection _serverVariables;
  public NameValueCollection ServerVariables
  {
    get
    {
      if (HttpRuntime.HasAspNetHostingPermission(AspNetHostingPermissionLevel.Low)) {
        return this.GetServerVars();
      }
      return this.GetServerVarsWithDemand();
    }
  }
  private NameValueCollection GetServerVars()
  {
    if (this._serverVariables == null) {
      this._serverVariables = new HttpServerVarsCollection(this._wr, this);
      if (!(this._wr is IIS7WorkerRequest)) {
        this._serverVariables.MakeReadOnly();
      }
    }
    return this._serverVariables;
  }
}

We can’t override ServerVariables since it’s not virtual and there is no setter. Digging deeper finds an internal HttpServerVarsCollection class which has the following Add signature:

public override void Add(string name, string value)
{
  throw new NotSupportedException();
}

The rabbit hole keeps getting deeper. Fortunately we find the AddStatic method which gives us some hope:

internal void AddStatic(string name, string value)
{
  if (value == null) {
    value = string.Empty;
  }
  base.InvalidateCachedArrays();
  base.BaseAdd(name, new HttpServerVarsCollectionEntry(name, value));
}

That looks promising. So let’s try making this work using reflection.

  var field = request.GetType()
                     .GetField("_serverVariables", BindingFlags.Instance | BindingFlags.NonPublic);
  if (field != null) {
    var variables = field.GetValue(request);
    var type = field.FieldType;
    if (variables == null) {
      var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null,
                                            new[] { typeof(HttpWorkerRequest), typeof(HttpRequest) }, null);
      variables = constructor.Invoke(new[] { null, request });
    }
    type.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)
        .SetValue(variables, false, null);
    var addStatic = type.GetMethod("AddStatic", BindingFlags.Instance | BindingFlags.NonPublic);
    addStatic.Invoke(variables, new[] { "REMOTE_ADDR", "127.0.0.1" });
    addStatic.Invoke(variables, new[] { "HTTP_USER_AGENT", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36" });
  }

SUCCESS! But we can do even better. How about we make this code extend the HttpRequest object and clean things up a little.

/// <summary>
/// Extension methods for the HttpRequest class.
/// </summary>
public static class HttpRequestExtensions
{
  /// <summary>
  /// Adds the name/value pair to the ServerVariables for the HttpRequest.
  /// </summary>
  /// <param name="request">The request to append the variables to.</param>
  /// <param name="name">The name of the variable.</param>
  /// <param name="value">The value of the variable.</param>
  public static void AddServerVariable(this HttpRequest request, string name, string value)
  {
    if (request == null) return;

    AddServerVariables(request, new Dictionary<string, string>() {
      { name, value }
    });
  }

  /// <summary>
  /// Adds the name/value pairs to the ServerVariables for the HttpRequest.
  /// </summary>
  /// <param name="request">The request to append the variables to.</param>
  /// <param name="collection">The collection of name/value pairs to add.</param>
  public static void AddServerVariables(this HttpRequest request, NameValueCollection collection)
  {
    if (request == null) return;
    if (collection == null) return;

    AddServerVariables(request, collection.AllKeys
                                          .ToDictionary(k => k, k => collection[k]));
  }

  /// <summary>
  /// Adds the name/value pairs to the ServerVariables for the HttpRequest.
  /// </summary>
  /// <param name="request">The request to append the variables to.</param>
  /// <param name="dictionary">The dictionary containing the pairs to add.</param>
  public static void AddServerVariables(this HttpRequest request, IDictionary<string,string> dictionary)
  {
    if (request == null) return;
    if (dictionary == null) return;

    var field = request.GetType()
                       .GetField("_serverVariables", BindingFlags.Instance | BindingFlags.NonPublic);
    if (field != null) {
      var type = field.FieldType;

      var serverVariables = field.GetValue(request);
      if (serverVariables == null) {
        var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null,
                                              new[] { typeof(HttpWorkerRequest), typeof(HttpRequest) }, null);
        serverVariables = constructor.Invoke(new[] { null, request });
        field.SetValue(request, serverVariables);
      }
      var addStatic = type.GetMethod("AddStatic", BindingFlags.Instance | BindingFlags.NonPublic);

      ((NameValueCollection) serverVariables).MakeWriteable();
      foreach (var item in dictionary) {
        addStatic.Invoke(serverVariables, new[] { item.Key, item.Value });
      }
      ((NameValueCollection)serverVariables).MakeReadOnly();
    }
  }
}

You might have noticed, that I also created a NameValueCollection extension to modify the IsReadOnly property. Of course, use this with care… “with great power comes great responsibility“. The creator of the NameValueCollection you’re consuming likely set the IsReadOnly property for a reason…

/// <summary>
/// Extension methods for the NameValueCollection class.
/// </summary>
public static class NameValueCollectionExtensions
{
  /// <summary>
  /// Retreives the IsReadOnly property from the NameValueCollection
  /// </summary>
  /// <param name="collection">The collection to retrieve the propertyInfo from.</param>
  /// <param name="bindingFlags">The optional BindingFlags to use. If not specified defautls to Instance|NonPublic.</param>
  /// <returns>The PropertyInfo for the IsReadOnly property.</returns>
  private static PropertyInfo GetIsReadOnlyProperty(this NameValueCollection collection, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic)
  {
    if (collection == null) return (null);
    return(collection.GetType().GetProperty("IsReadOnly", bindingFlags));
  }

  /// <summary>
  /// Sets the IsReadOnly property to the specified value.
  /// </summary>
  /// <param name="collection">The collection to modify.</param>
  /// <param name="isReadOnly">The value to set.</param>
  private static void SetIsReadOnly(this NameValueCollection collection, bool isReadOnly)
  {
    if (collection == null) return;

    var property = GetIsReadOnlyProperty(collection);
    if (property != null) {
      property.SetValue(collection, isReadOnly, null);
    }
  }

  /// <summary>
  /// Makes the specified collection writable via reflection.
  /// </summary>
  /// <param name="collection">The collection to make writable.</param>
  public static void MakeWriteable(this NameValueCollection collection)
  {
    SetIsReadOnly(collection, false);
  }

  /// <summary>
  /// Makes the specified collection readonly via reflection.
  /// </summary>
  /// <param name="collection">The collection to make readonly.</param>
  public static void MakeReadOnly(this NameValueCollection collection)
  {
    SetIsReadOnly(collection, true);
  }
}

And there you have it. A way to add ServerVariables. Keep in mind that this code is extremely fragile because it’s using reflection to access the internal workings of code that we don’t have control over. Below are examples of using the extension method.

public class Example
{
  public void Test() 
  {
    string url = "http://127.0.0.1";
    var request = new HttpRequest(String.Empty, url, String.Empty);
    request.AddServerVariable("REMOTE_ADDR", "127.0.0.1");

    // or
    
    request.AddServerVariables(new Dictionary<string, string>() {
      { "REMOTE_ADDR", "127.0.0.1" },
      { "HTTP_USER_AGENT", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36" }
    });
  }
}

I hope you find this useful and it can save you some time.

Who doesn’t love LINQ? Who doesn’t love extension methods in .NET?

Unfortunately, Microsoft could have made things easier for developers by handling nulls gracefully. What do I mean? Take the following code as an example:

IEnumerable<int> numbers = null;
if (numbers.Any()) {
}

Obviously, as a programmer you would know that you should get a NullReferenceException from the above code. Below is the correct way to write that function:

IEnumerable<int> numbers = null;
if ((numbers != null) && (numbers.Any())) {
}

Simple and logical fix, but it’s just “noisy”. When dealing with an extension method, I disagree with how null references were handled in the LINQ libraries. Any extension function should detect null and return early (when possible). I believe that Microsoft’s view and defense is that an IEnumerable should never be “null” but instead be an empty collection.

Below is the decompiled implementation of the .Any() LINQ extension method (courtesy of .Net Reflector v6).

[__DynamicallyInvokable]
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) {
    throw Error.ArgumentNull("source");
  }
  using (IEnumerator<TSource> enumerator = source.GetEnumerator()) {
    if (enumerator.MoveNext()) {
      return true;
    }
  }
  return false;
}

The following simple change to the above method would remove all of that extra “noise” and make our code easier to read.

[__DynamicallyInvokable]
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) return(false);
  using (IEnumerator<TSource> enumerator = source.GetEnumerator()) {
    if (enumerator.MoveNext()) {
      return true;
    }
  }
  return false;
}

Of course, the other solution (and recommended best practice) is to not return null from a function that returns an IEnumerable, but to instead return an empty collection. Unfortunately, when dealing with other people’s code or libraries you may not have that luxury. Below is a simple and efficient example of how to NOT return null for an IEnumerable result. Please note that the example is contrived and a String.Split function already exists.

public IEnumerable<string> Split(string input, string value) {
  if (input == null) return(new string[0]);
  ...
}

So when you’re writing you own extension methods in .NET, do the world a favor and handle null much better.