Posts in category .net - page 18

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

Italic syntax highlighting in Visual Studio 2005

I came across a posting by Thomas Restrepo about a theme for Vim he likes called Wombat and how it wouldn’t be worth porting to Visual Studio as it doesn’t support italic syntax highlighting – as we all know.

This got me thinking and I was able to port it with italics although the process is a bit of a hack.

If I can figure out a way of making this hack re-distributable without infringing on copyrights I’ll follow this one up.

In the meantime here’s a screen-shot of it in action using Consolas.

Visual Studio 2005 with italics

I can’t stand using vim for .NET – I’ve got better things to do than commit the entire .NET Framework to memory. I remember watching a WPF screen-cast where the guy was using “his trusty editor” (vim or emacs – I forget ;-) and going on about the great keyboard short-cuts whilst constantly trying different method names, compiling yet again and finally looking up help in the absence of IntelliSense.

I did however check out the latest trunk of SharpDevelop this weekend and was quite impressed with both the product and the source code. There was a bit of flickering with the solution explorer and the icons seem to be a bit of a steal-and-mash but otherwise looks first class.

[)amien

CodeSmith template to generate LINQ To SQL Data Context

If you are interested in what LINQ to SQL generates and don’t have Orcas installed or available right now but use CodeSmith try the following template to generate very similar code.

The primary difference is that this writes out the System types rather than the C# aliases (e.g. System.Int32 instead of int) but that could easily be changed but is binary compatible and otherwise almost identical to the source.

Download LINQ to SQL template (CodeSmith) (4 KB)

<%@ CodeTemplate Src="LinqFunctions.cs" Inherits="LinqFunctions" Language="C#" TargetLanguage="C#"
  Description="Generates a data context and entities for given tables." %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Assembly Name="System.Data" %>
<%@ Import Namespace="SchemaExplorer" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Data" %>
<%@ Property Name="DataContextClassName" Type="System.String" Category="Naming" Optional="True"Description="Name of the data context class to generate." %>
<%@ Property Name="Namespace" Type="System.String" Category="Naming" Optional="True" Description="Namespace for the data context class to generate." %>
<%@ Property Name="SourceTables" Type="SchemaExplorer.TableSchemaCollection" Category="Connection"Description="Tables to be mapped." %>
<%SchemaExplorer.DatabaseSchema database = SourceTables[0].Database;
string className = (String.IsNullOrEmpty(DataContextClassName)) ? DatabaseName(database) + "DataContext" : DataContextClassName;
Dictionary<TableSchema, List<TableKeySchema>> reverseForeignKeys = new Dictionary<TableSchema, List<TableKeySchema>>();
foreach(TableSchema table in SourceTables) {
    foreach(TableKeySchema keySchema in table.ForeignKeys) {
        if (reverseForeignKeys.ContainsKey(keySchema.PrimaryKeyTable)) {
            reverseForeignKeys[keySchema.PrimaryKeyTable].Add(keySchema);
        }
        else {
            List<TableKeySchema> keySchemas = new List<TableKeySchema>();
            keySchemas.Add(keySchema);
            reverseForeignKeys.Add(keySchema.PrimaryKeyTable, keySchemas);
        }
    }
}%>//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     CodeSmith template DeLINQuent.cst v0.1
//     Generated by <%=CurrentUserName()%> at <%=DateTime.Now%>
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace <%=Namespace%>{
    public partial class <%=className%> : global::System.Data.Linq.DataContext
    {
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public <%=className%>(string connection) :
                base(connection)
        {
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public ProjectDataContext(global::System.Data.IDbConnection connection) :
                base(connection) {
        }
<%    foreach(TableSchema table in SourceTables) {
        string entityName = EntityName(table);
        string propertyName = TableName(table);%>
        public global::System.Data.Linq.Table<<%=entityName%>> <%=propertyName%> {
            get {
                return this.GetTable<<%=entityName%>>();
            }
        }
<%     } %>
    }<%    foreach(TableSchema table in SourceTables) {
        string entityName = EntityName(table);%>
    [global::System.Data.Linq.Table(Name="<%=table.FullName%>")]
    public partial class <%=entityName%> :global::System.Data.Linq.INotifyPropertyChanging,global::System.ComponentModel.INotifyPropertyChanged
    {
<%    foreach(ColumnSchema column in table.Columns) { %>
        private <%=NullableSystemType(column)%> _<%=PropertyName(column)%>;
<%    }
      if (table.ForeignKeys.Count > 0) { %>
<%        foreach(TableKeySchema keySchema in table.ForeignKeys) {
            string foreignEntityName = EntityName(keySchema.PrimaryKeyTable);%>
        private global::System.Data.Linq.EntitySet<<%=foreignEntityName%>> _<%=foreignEntityName%>;<%
          }
      }
      if (reverseForeignKeys.ContainsKey(table)) { %>
<%        foreach(TableKeySchema keySchema in reverseForeignKeys[table]) {
            string propertyName = TableName(keySchema.ForeignKeyTable);%>
        private global::System.Data.Linq.EntityRet<<%=EntityName(keySchema.ForeignKeyTable)%>> _<%=propertyName%>;
<%        }
      } %>
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public <%=entityName%>() {
        }
<%    foreach(ColumnSchema column in table.Columns) {
        string propertyName = PropertyName(column);%>
        [global::System.Data.Linq.Column(Storage="_<%=propertyName%>", Name="<%=column.Name%>", DBType="<%=AttributeColumnDbType(column)%>"<%
            if (column.IsPrimaryKeyMember) { %>, IsPrimaryKey=true<% }
            if (!column.AllowDBNull) { %>, CanBeNull=false<% } %>)]
        public <%=NullableSystemType(column)%> <%=propertyName%> {
            get {
                return this._<%=propertyName%>;
            }
            set {
                if (this._<%=propertyName%> != value) {
                    this.OnPropertyChanging("<%=propertyName%>");
                    this._<%=propertyName%> = value;
                    this.OnPropertyChanged("<%=propertyName%>");
                }
            }
        }

<%    } %>
<%    foreach(TableKeySchema keySchema in table.ForeignKeys) {
        string propertyName = EntityName(keySchema.PrimaryKeyTable);
        string tableName = TableName(keySchema.PrimaryKeyTable);%>
        [global::System.Data.Linq.Association(Name="<%=keySchema.Name%>",
            Storage="_<%=propertyName%>", OtherKey="<%=AttributeColumnList(keySchema.ForeignKeyMemberColumns)%>",
            ThisKey="<%=AttributeColumnList(keySchema.PrimaryKeyMemberColumns)%>", IsForeignKey=true)]
        public <%=propertyName%> <%=propertyName%> {
            get {
                return this._<%=propertyName%>.Entity;
            }
            set {
                if (this._<%=propertyName%>.Entity != value) {
                    this.OnPropertyChanging("<%=propertyName%>");
                    if ((this._<%=propertyName%>.Entity != null)) {
                        <%=propertyName%> temp = this._<%=propertyName%>.Entity;
                        this._<%=propertyName%>.Entity = null;
                        temp.<%=TableName(table)%>.Remove(this);
                    }
                    this._<%=propertyName%>.Entity = value;
                    if ((value != null)) {
                        value.<%=TableName(table)%>.Add(this);
                    }
                    this.OnPropertyChanged("<%=propertyName%>");
                }
            }
        }
<%    } %>
<%    if (reverseForeignKeys.ContainsKey(table)) {
        foreach(TableKeySchema keySchema in reverseForeignKeys[table]) {
            string propertyName = TableName(keySchema.ForeignKeyTable);%>
        [global::System.Data.Linq.Association(Name="<%=keySchema.Name%>",
            Storage="_<%=propertyName%>", OtherKey="<%=AttributeColumnList(keySchema.PrimaryKeyMemberColumns)%>",
            ThisKey="<%=AttributeColumnList(keySchema.ForeignKeyMemberColumns)%>")]
        public IEnumerable<<%=EntityName(keySchema.ForeignKeyTable)%>> <%=propertyName%> {
            get {
                return this._<%=propertyName%>;
            }
        }

<%             }
        } %>
        public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanging;
        public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        protected void OnPropertyChanging(string propertyName) {
            if (this.PropertyChanging != null)
                this.PropertyChanging(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }

[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        protected void OnPropertyChanged(string propertyName) {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
<%     } %>
}

[)amien

Discarding new entity objects in LINQ to SQL beta 1

An unusual bug in .NET Framework 3.5 beta 1 that relates to creating a new entity object in LINQ to SQL has been getting in my way.

Bug description

When you create a new entity object it is not automatically added to the database unless you either attach it to the table or create an association to another existing entity.

The problem arises when you decide to discard the new object instead of persisting it. Setting the property of the association(s) to null should achieve this and the LINQ to SQL designer code shows that it removes the new object from the association.

When SubmitChanges is called it is apparent that the new object has not been discarded but is in fact attempting to persist to the database.

Reproduction

  1. Create a new C# Console project
  2. Add a new a LINQ to SQL file and name it Northwind
  3. Use Server Explorer to drag Orders and Customers tables from a Northwind database connection
  4. Paste the following code snippet into the static main method
  5. Run the program
  6. Examine the Orders table and notice a new a record consisting of null fields
NorthwindDataContext dc = new NorthwindDataContext();
Customer alfredCustomer = dc.Customers.Single(c => c.CustomerID == "ALFKI");
Order newOrder = new Order();
newOrder.Customer = alfredCustomer;
newOrder.Customer = null;
dc.SubmitChanges();

Workaround

It would appear there is at least a temporary workaround which is to force the association that was modified to reload. You can do this by calling the associations Load method after the entity has been de-associated but before SubmitChanges.

In our reproduction this would therefore be:

newOrder.Customer = null;
alfredCustomer.Orders.Load();
dc.SubmitChanges();

Calling Load does not disrupt other still-valid associations that have not yet persisted.

Curiously accessing the Count property of the association also causes the problem to disappear because the EntitySet<T> class that is responsible for representing these one-to-many associations calls Load every time you access Count. This is a potential performance hit as accessing a Count` property is considered to be a fast operation that does not go off and rebuild collections.

Here’s hoping that Microsoft fix those two bugs before release and ideally before beta 2.

[)amien