Posts tagged with c - page 10

Partial methods in .NET 3.5, overview and evolution

One of the interesting new things in .NET 3.5 is partial methods which are now being used extensively by LINQ to SQL and no-doubt will be Microsoft’s corner-post of extensibility for generated classes. Here’s a quick overview:

Extending generated code via inheritance (.NET 1.1)

When inheriting from generated classed designers often provide virtual methods for you to override and extend at a cost of being forced to inherit from the generated class instead of one of your own choosing. e.g.

class Customer {
    private string name;
    public string Name {
        set {
            name = value;
            OnNameChanged();
        }
    }
    protected virtual void OnNameChanged() {
    }
}

class Customer : CustomerGenerated {
    protected override void OnNameChanged() {
        DoSomething();
    }
}

Extending generated code via delegates/event handlers (.NET 2.0)

.NET 2.0 introduced partial classes and code generation moved into separate files that were pulled together at compile-time to provide a single consistent class leaving you free to choose your own inheritance.

The drawback is that you can not override generated methods because they are not inherited but rather merged at compile-time.

This means if you want to provide extensibility in your generated code you will either need to use inheritance or generate delegates/events that consumers of your class can hook up. This works but is not intuitive as demonstrated:

partial class Customer {
  protected event EventHandler NameChangedHandler;
  private string name;
  public string Name {
    set {
      name = value;
      if (NameChangedHandler != null)
          NameChangedHandler.Invoke(this, null);
    }
  }
}

partial class Customer {
  public Customer() {
    NameChangedHandler += OnNameChanged;
  }

  protected void OnNameChanged(object sender, EventArgs e) {
    DoSomething();
  }
}

Extending generated code via partial methods (.NET 3.5)

.NET 3.5 sees Microsoft answer the problem by allowing the partial keyword to be used at method level instead of just at class level:

partial class Customer {
  private string name;
  public string Name {
    set {
      name = value;
      OnNameChanged();
    }
  }
  partial void OnNameChanged();
}

partial class Customer {
  partial void OnNameChanged() {
    DoSomething();
  }
}

Thoughts

This is clearer than using delegates & events but still suffers some limitations:

  • Only one method can have a body so you can’t override generated code
  • Properties are not supported

If .NET supported multiple inheritance then partial classes and methods would be redundant. As Microsoft tout interfaces and composition as a better approach it is about time they added support to automatically wire up an interface to a compound object (a blog post for another time).

[)amien

Rails-style controllers for ASP.NET

Rob Conery has been putting together some great screen casts on SubSonic and his latest on generating controllers pointed out that ASP.NET doesn’t support the Rails-style http://site//controller/method style of execution.

This got me quite excited and I’ve put together a proof-of-concept web project that demonstrates mapping the path to controller methods using an IHttpHandler and reflection.

How it works

It registers the ControllerHttpHandler via the web.config:

<httpHandlers>
  <add path="/ctl/\*/\*" verb="POST,GET,HEAD" type="ControllerHttpHandler" />
</httpHandlers>

There is a very basic Controller abstract base class that just provides a simple way of accessing the context for dealing with request/response for now.

public abstract class Controller
{
  protected System.Web.HttpContext context;

  internal Controller(System.Web.HttpContext context) {
    this.context = context;
  }
}

We then have a test controller or two that implement from this with a couple of methods and the required constructor:

public class TestController : Controller
{
  public TestController(System.Web.HttpContext context) : base(context) { }

  public void Index() {
    context.Response.Write("This is the index");
  }

  public void Welcome() {
    context.Response.Write("Welcome to the TestController");
  }
}

Finally the magic that joins them up is the ControllerHttpHandler:

using System;
using System.Web;
using System.Reflection;

public class ControllerHttpHandler : IHttpHandler
{
  public void ProcessRequest(HttpContext context) {
    string[] parts = context.Request.Path.Split('/');
    if (parts.Length < 4) {
      context.Response.Write("No controller & member specified");
      return;
    }

    string controllerName = parts[2];
    string methodName = parts[3];
    Type potentialController = Type.GetType(controllerName);
    if (potentialController != null && potentialController.IsClass && potentialController.IsSubclassOf(typeof(Controller))) {
      MethodInfo potentialMethod = potentialController.GetMethod(methodName);
      if (potentialMethod != null) {
        Controller controller = (Controller) Activator.CreateInstance(potentialController, context);
        potentialMethod.Invoke(controller, null);
      }
      else
        context.Response.Write(String.Format("Method '{0}' not found on controller '{1}'", methodName, controllerName));
    }
    else
      context.Response.Write(String.Format("Controller '{0}' not found", controllerName));
  }

  public bool IsReusable {
    get { return false; }
  }
}

That’s it!

Limitations

The controllers and methods are mapped at run-time using reflection. This would probably be too slow for production. Also it currently has to be in a top-level folder because I can’t figure out how to pass the HTTP request back to ASP.NET to try with the rest of the stack if we don’t have a matching controller/method.

One option might be to have no httpHandlers in the web.config and add the exact controller/method maps at build or run-time. This solves both the top-level problem and potentially the speed.

Another option to address just the speed of reflection would be to cache the path/method strings to the actual method and type so the only reflection would be the Activator.CreateInstance. If that is slow then we could look at pooling the controller instances themselves.

Going forward

Parameters for a method could be extracted and parsed from the query-string – they are currently ignored.

Response is raw output – we could do something very similar to rhtml.

I’m going to chat things over with the Subsonic team and see if we can come up with anything from here.

[)amien

WinForms tricks & tips

TreeView right-mouse button select the node

I’m not sure why it doesn’t do this as standard but a simple event handler should do the trick:

private void treeView_MouseDown(object sender, MouseEventArgs e) {
    if (e.Button == MouseButtons.Right) {
    TreeNode node = treeView.GetNodeAt(e.X, e.Y);
    if (node != null)
        treeView.SelectedNode = node;
    }
}

Adding text to a text box

People have asked (in the IRC #CSharp) why adding text to a TextBox is so slow and flickery. Normally they are trying:

textBox1.Text += myNewText;

The problem with this is that it copies all the Text from the text box then adds myNewText to it and copies the whole result back. This is because strings in .NET are immutable, i.e. can’t be changed, and so adding one string to another always results in this overhead (and hence the existence of the StringBuilder class).

The solution is to abandon the slow, inefficient string concatenation and use the method AppendText thusly:

textBox1.AppendText(myNewText);

Which is fast and efficient whilst also being available to TextBox, RichTextBox and MaskedTextBox (by virtue of being a method of BaseTextBox).

They usually also ask how to make the text box scroll to the end. Just use the following line:

textBox1.ScrollToCaret();

Don’t forget keyboard input

Check those tab orders and accelerator keys!

Creating dynamic controls

If you’re ever unsure how to work with a dynamic control just create it in Visual Studio’s Designer and then head into the .designer.cs file and examine the code it generates.

Windows Forms FAQ

There are many other hints, tips and solutions in the Windows Forms FAQ.

[)amien

Parameterising the IN clause of an SQL SELECT in .NET

I’m a fan of parameterized queries with a strong dislike for building SQL (or other magic strings). Encoding, escaping errors, localization formatting problems and injection can run rampant when you think everything is a string.

Problem

Today I found myself a quandary as I needed to SELECT records based on a list of values I had. e.g.

SELECT * FROM Products WHERE ProductCode IN ('ABC123', 'DEF456', 'GHI789')

At first glance the relevant parameterized version might look like:

SELECT * FROM Products WHERE ProductCode IN (@productlist)

The problem here however is that if you put a comma-separate list of items into a string parameter named @productlist then it sends this to the database server:

SELECT * FROM Products WHERE ProductCode IN ('ABC123, DEF456, GHI789')

That’s not what we want at all.

This hack isn’t pretty and it has some limitations:
  • Only works with named parameters
  • Could upset some DB providers that don’t like having command parameters removed or command text modified
  • Parameter name to replace must be totally unique – i.e. not exist as a subset of another parameter name
  • Only preserves the basic IDbCommand declared properties

It should however work across DB providers and types.

Usage

The previous example would mean we use exactly the expected parameterized version with @productlist in place.

Add the parameter as you’d expect but instead of assigning a string/numeric to it assign something IEnumerable.

Finally call this method against the command and parameter before you execute it for the ‘magic’ to happen:

The ‘magic’

public void ExpandDbArrayParameter(IDbCommand cmd, IDbDataParameter parameter) {
  if (parameter.Value is IEnumerable) {
    int index = 0;
    StringBuilder newParameterSQL = new StringBuilder();
    foreach(Object value in (IEnumerable) parameter.Value) {
      String valueParameterName = String.Format("{0}{1}", parameter.ParameterName, ++index);
      IDataParameter valueParameter = cmd.CreateParameter();
      valueParameter.DbType = parameter.DbType;
      valueParameter.Direction = parameter.Direction;
      valueParameter.ParameterName = valueParameterName;
      valueParameter.SourceColumn = parameter.SourceColumn;
      valueParameter.SourceVersion = parameter.SourceVersion;
      valueParameter.Value = value;
      cmd.Parameters.Add(valueParameter);

      if (index == 1)
        newParameterSQL.Append(valueParameterName);
      else
        newParameterSQL.Append("," + valueParameterName);
      }
      cmd.Parameters.Remove(parameter);
      cmd.CommandText = cmd.CommandText.Replace(parameter.ParameterName, newParameterSQL.ToString());
    }
}

[)amien