in

ljusberg.se

Smöråkning

October 2007 - Posts

  • How cool is this?

    I'm currently writing this post on the express bus to Redmond using my iPod touch and the free WiFi provided on the bus. In my book it doesn't get much cooler than that!
    Posted Oct 27 2007, 01:44 AM by anders with no comments
    Filed under:
  • T4 editor available

    It seems Clarius Consulting have released a beta version of a pretty nice addition to Visual Studio, at least if you're working with the Guidance Automation Toolkit. The T4 Editor will give you IntelliSense, nice colorization etc when working with your T4 templates.

    I'm now just waiting for them to release a version of the Software Factories Toolkit that works with VS2008 Beta 2..!

  • VS2008 RTM any day now?

    According to the Microsoft guys in building 20, the RTM of Visual Studio 2008 and .NET Framework 3.5 is coming "very, Very, VERY soon" - whatever that means...

  • Going to Redmond!

    Well, in a couple of days I'm off to the Microsoft Campus in Redmond! I'm attending an Orcas Adoption Workshop next week which will take me through all the new stuff in Visual Studio 2008 and .NET Framework 3.5. So far, I haven't got the agenda so I have more or less no idea who I'll meet and what kind of workshops to expect but I'm sure it'll be great.

    I'm really, really looking forward to it. Actually visiting the Campus is something I've been dreaming about for quite some time (yes, I know.. maybe I should try to reprioritize my dreams... but still!) 

    So, is anyone else going to be in the Redmond area next week?

  • Caching dynamic controls in ASP.NET - solution

    In my last post, I asked for help with a problem we've been having with caching of dynamic controls - and I got some from Dave Reed. Dave works for the ASP.NET team and if his blog isn't on your roll yet, add it ASAP - it's simply brilliant.

    Dave explained the problem:


    Apparently, the stack trace involved with loading the control is taken into account when deciding how to cache the controls output. So for example, if you load the control twice like so:
    LoadControl("foo.ascx"); // once 
    LoadControl("foo.ascx"); // twice
    Then they will be cached separately. But if you load them like this:
    MethodThatLoadsFoo(); // once 
    MethodThatLoadsFoo(); // twice
     
    Where MethodThatLoadsFoo contains:
    LoadControl("foo.ascx"); 
    They are cached together because it will appear that they were loaded via the same code line.
     
    Since you are loading them dynamically based on data you probably have a single method that loads whatever control it is to be. But depending on the details of your implementation perhaps you can find a way to have a different LoadControl reference load each one?


    Ok.. So where does that leave us? Remember, the implementation that calls LoadControl in my last example looks like this:

    foreach (ControlInfo controlInfo in controlList)
    {
       // Load the control and add it to the page
       HttpContext.Current.Items["uniqueId"] = controlInfo.UniqueControlId;
       Control ctrl = LoadControl(controlInfo.ControlUrl);
       PlaceHolder1.Controls.Add(ctrl);
    }

    It is the same physical line of code that loads all controls, which makes the caching mechanism believe that it is the very same control that is being loaded. In order for it to cache multiple versions, I'd need to add a new LoadControl line for each control. So I guess I could "unwrap" the foreach-loop and do something like:

    if (controlList.Count > 0)
    {
       HttpContext.Current.Items["uniqueId"] = controlList[0].UniqueControlId;
       PlaceHolder1.Controls.Add(LoadControl(controlList[0].ControlUrl));
    }
    if (controlList.Count > 1)
    {
       HttpContext.Current.Items["uniqueId"] = controlList[1].UniqueControlId;
       PlaceHolder1.Controls.Add(LoadControl(controlList[1].ControlUrl));
    }
    if (controlList.Count > 2)
    {
       HttpContext.Current.Items["uniqueId"] = controlList[2].UniqueControlId;
       PlaceHolder1.Controls.Add(LoadControl(controlList[2].ControlUrl));
    }

    Ehh.. Nope.. Not gonna happen...

    The only other alternative I could think of was to dynamically generate and compile the code that is used to render a page. This is (a crude version of) what I came up with:

    private void CompileAndInitializePage(Page page, List controlList)
    {
      Assembly compiledAssembly;
    
      // Has this page been built before? If so, there should already be an existing assembly
      // compiled for it.
      if (compiledAssemblies.ContainsKey(page.AppRelativeVirtualPath))
      {
        compiledAssembly = compiledAssemblies[page.AppRelativeVirtualPath];
      }
      else
      {
        // If not, compile a new one.
        string mainHeader =
          "using System.Web;\n" +
          "using System.Web.UI;\n" +
    
          "namespace DynamicCacheTest\n" +
          "{\n" +
          " public class TempPageBuilder\n" +
          " {\n" +
          " public void BuildPage(Page page)\n" +
          " {\n" +
          " Control holder;\n" +
          " holder = page.FindControl(\"PlaceHolder1\");\n";
    
        string mainFooter =
          " }\n" +
          " }\n" +
          "}\n";
        StringBuilder sb = new StringBuilder(mainHeader);
        foreach (ControlInfo ctrl in controlList)
        {
          sb.AppendFormat("HttpContext.Current.Items[\"uniqueId\"] = {0};\n", ctrl.UniqueControlId);
          sb.AppendFormat("holder.Controls.Add(page.LoadControl(\"{0}\"));\n", ctrl.ControlUrl);
        }
        sb.Append(mainFooter);
    
        ICodeCompiler compiler = new CSharpCodeProvider().CreateCompiler();
    
        CompilerParameters parameters = new CompilerParameters(new string[] { "System.dll", "System.Web.dll" });
        CompilerResults res = compiler.CompileAssemblyFromSource(parameters, sb.ToString());
        compiledAssembly = res.CompiledAssembly;
        compiledAssemblies[page.AppRelativeVirtualPath] = compiledAssembly;
      }
    
      // Build the page builder object
      object builder = Activator.CreateInstance(compiledAssembly.GetType("DynamicCacheTest.TempPageBuilder"));
    
      // Invoke the BuildPage method
      builder.GetType().InvokeMember("BuildPage", BindingFlags.InvokeMethod, null, builder, new object[] { this });
    }

    And it works beautifully! I'm not too worried about performance since I'm only compiling the page once, and running the compiled code shouldn't be slower than the foreach-loop is today. The only thing I'm thinking is that with many pages on a site I'd have a lot of very small assemblies loaded. Probably it would be a better idea to precompile all pages on startup and put them in a single assembly.

    Anyway, in the end we've decided that we won't actually put this in production since the problem probably is better worked around by educating our webmasters, but it was nice to finally find a solution.

    Update: I've attached the updated source code to this post.

    Update 2: And now I'm trying out a Live Writer Plugin to format the source code

    Posted Oct 17 2007, 09:39 AM by anders with 6 comment(s)
    Filed under: ,
Powered by Community Server (Non-Commercial Edition), by Telligent Systems