Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

A (less) simple include for ASP.NET

(c) 2003 Bertrand Le Roy In yesterday’s post, I published the code for a simple include method for ASP.NET that I’ve been using in a couple of places, only to realize that it was fine for what I was doing but probably not very useful beyond that.

So I spent some time today broadening its scope. It now supports nested includes (I modified the original post to reflect that change) and also setting properties on the control.

You can still do plain includes:

<% this.Include("contents.ascx"); %>

But now you can also set properties on the included user control using an anonymous object:

<% this.Include("contents.ascx", new {number=2, what="A"}); %>

The code for the helper considerably grew along the way but it’s still reasonable:

using System;
using System.Linq;
using System.Reflection;
using System.Web.UI;

public static class IncludeHelper {
    public static void Include(this TemplateControl host,
string virtualPath) { Include(host, virtualPath, null); } public static void Include(this TemplateControl host,
string virtualPath,
object properties) { var resolvedPath = host.ResolveUrl(virtualPath); var include = host.LoadControl(resolvedPath); if (properties != null) { var includeType = include.GetType(); foreach (var fieldInfo in
properties.GetType().GetProperties()) { var targetInfo = includeType.GetMember(
fieldInfo.Name, MemberTypes.Field |
MemberTypes.Property, BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public) .FirstOrDefault(); var targetPropertyInfo = targetInfo as PropertyInfo; if (targetPropertyInfo != null) { targetPropertyInfo.SetValue(
include,
fieldInfo.GetValue(properties, null),
null); } else { var targetFieldInfo = targetInfo as FieldInfo; if (targetFieldInfo != null) { targetFieldInfo.SetValue(
include,
fieldInfo.GetValue(properties, null)); } else { throw new ArgumentException( "There is no property of field with " +
"that name on the target control."
, fieldInfo.Name); } } } } using (var writer =
new HtmlTextWriter(host.Page.Response.Output)) { include.RenderControl(writer); } } }

I hope this helps. Again, if you are OK with the regular syntax to include a user controls, you probably shouldn’t use this and stick to what you know. I built this because I’m not entirely satisfied with that syntax and because I have a clear use case where it makes more sense. Make your own opinion and follow it :)

Note: The control is still not going into the control tree, which breaks lots of stuff. I don't really care that much about it but it may be important to you so I thought I'd point it out.

Comments

Joe Chung said:

Cool, this is looking a lot like Rails' partial views.  It would have a lot less Reflection cruft if you used C# 4.0 dynamics ... or if you wrote it in VB. ;)

# July 10, 2009 9:07 PM

erkekkedi said:

Why is it that people don't prefer to Register their ascx file but persistently use include instead?

# July 12, 2009 5:40 AM

Arun Mahendrakar said:

This is really interesting. I don't see a need for it right now, but I'm sure it's an awesome thing to know.

# July 12, 2009 1:25 PM

Brett said:

I have done this for a while now as i deal with a graphic designer who doesn't want me futzing too much with the aspx files...

1. Add a UserControl that just has this:

public string Path { get; set; }

protected void Page_Load(object sender, EventArgs e)

{

  this.Controls.Add(LoadControl(Path));

}

2. In web.config

 <pages>

   <controls>

     <add tagPrefix="x" assembly="y" namespace="z" />

   </controls>

 </pages>

3. On any aspx page - regardless of the existence of the @Page directive or control registration etc....all you do is add the glabally registered control and enter the path of the control ... <x:Control runat=server Path="~/TheControl.ascx" /> - postback etc. works as normal

4. ???

5. Profit.

# July 12, 2009 8:10 PM

Bertrand Le Roy said:

@Brett: yes, I'm preparing the next post along those lines, except you'll also be able to set properties for the included control. :) And maybe something else after that...

# July 13, 2009 1:05 AM

Joel Coehoorn said:

I don't know: if you use this much it looks like an awful lot of extra reflection code to go through at runtime.  Could be a real performance hog for not a lot of syntax savings.  I'd like to see some load testing.

# July 13, 2009 1:23 PM

Brett said:

Just replace this as the UserControl in my comments above...

System.Web.UI.Control control=LoadControl(Path);

foreach (string key in this.Attributes.Keys)

{

 PropertyInfo pinfo = control.GetType().GetProperty(key);

 pinfo.SetValue(control, Convert.ChangeType(this.Attributes[key], pinfo.PropertyType), null);

}

this.Controls.Add(control);

then you can add properties like

<x:Control runat=server Path="~/TheControl.ascx" idx="1" category="books" />

Good for standard types.

# July 13, 2009 3:54 PM

Bertrand Le Roy said:

@Brett: yep. Still not getting IntelliSense on those properties. Working on an interesting solution, going almost full-circle to where we started...

# July 13, 2009 4:44 PM

Jonas Eriksson said:

Interesting read - I stumbled on using code blocks in Web user controls when I wanted to use one inside another, then the code blocks syntax was not evaluated. Perhaps this will do the trick for me.

This didnt work:

UserControl1.Ascx:

<p><uc1:UserControl2 ID="uc2" runat="server" Text="<%=MyObj%>"/></p>

UserControl2.Ascx: (has public property "Text")

<% if (this.Text!="") {%><p><%=this.Text/></p><%}%>

# September 29, 2010 3:38 PM

Bertrand Le Roy said:

@Jonas: well the reason why your code doesn't work is fairly simple: to set-up a server control's properties, you need to use databinding syntax (<%# %> instead of <%= %>) and you need to call databind on the control.

# September 29, 2010 3:55 PM