Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

April 2012 - Posts

URL Related Expression Builders

Well, it has been quite a while since I talked about expression builders. If you haven’t already, I suggest you have a look at that, I think you will find interesting stuff there. Because I think this is an important topic for ASP.NET, I have two new expression builders for you:

ThemeFileUrlExpressionBuilder Returns a file URL relative to the current theme (/App_Themes/ThemeName)
WebResourceUrlExpressionBuilder Returns the URL for an embedded resource file in the current or another assembly

ThemeFileUrlExpressionBuilder may be useful when you have different themes (probably even switching them dynamically) and you want to reference a file that is relative to the current theme, without having it hardcoded.

As for WebResourceUrlExpressionBuilder, it retrieves the URL for a file included in an assembly as a web embedded resource.

As usual, the code first:

   1: [ExpressionPrefix("ThemeFileUrl")]
   2: public class ThemeFileUrlExpressionBuilder : ExpressionBuilder
   3: {
   4:     public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
   5:     {
   6:         if (String.IsNullOrEmpty(entry.Expression) == true)
   7:         {
   8:             return (new CodePrimitiveExpression(String.Empty));
   9:         }
  10:         else
  11:         {
  12:             return (new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(this.GetType()), "GetThemeUrl"), new CodePrimitiveExpression(entry.Expression)));
  13:         }
  14:     }
  15:  
  16:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
  17:     {
  18:         if (String.IsNullOrEmpty(entry.Expression) == true)
  19:         {
  20:             return base.EvaluateExpression(target, entry, parsedData, context);
  21:         }
  22:         else
  23:         {
  24:             return (GetThemeUrl(entry.Expression));
  25:         }
  26:     }
  27:     
  28:     public static String GetThemeUrl(String fileName)
  29:     {
  30:         Page page = HttpContext.Current.Handler as Page;
  31:  
  32:         if (page != null)
  33:         {
  34:             String path = String.Concat("/App_Themes/", page.Theme, "/", fileName);
  35:  
  36:             return (path);
  37:         }
  38:  
  39:         return (String.Empty);
  40:     }
  41:  
  42:     public override Boolean SupportsEvaluate
  43:     {
  44:         get
  45:         {
  46:             return (true);
  47:         }
  48:     }
  49: }
   1: [ExpressionPrefix("WebResourceUrl")]
   2: public class WebResourceUrlExpressionBuilder : ExpressionBuilder
   3: {
   4:     public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
   5:     {
   6:         if (String.IsNullOrEmpty(entry.Expression) == true)
   7:         {
   8:             return (new CodePrimitiveExpression(String.Empty));
   9:         }
  10:         else
  11:         {
  12:             String[] parts = entry.Expression.Split(',');
  13:  
  14:             if (parts.Length == 2)
  15:             {
  16:                 return (new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(this.GetType()), "GetWebResourceUrl"), new CodePrimitiveExpression(parts[0]), new CodePrimitiveExpression(parts[1])));
  17:             }
  18:             else
  19:             {
  20:                 return (new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(this.GetType()), "GetWebResourceUrl"), new CodePrimitiveExpression(entry.Expression)));
  21:             }
  22:         }
  23:     }
  24:  
  25:     public override Object ParseExpression(String expression, Type propertyType, ExpressionBuilderContext context)
  26:     {
  27:         if (String.IsNullOrEmpty(expression) == true)
  28:         {
  29:             return (base.ParseExpression(expression, propertyType, context));
  30:         }
  31:         else
  32:         {
  33:             String[] parts = expression.Split(',');
  34:  
  35:             if (parts.Length == 2)
  36:             {
  37:                 return (GetWebResourceUrl(parts[0], parts[1]));
  38:             }
  39:             else
  40:             {
  41:                 return (GetWebResourceUrl(expression));
  42:             }
  43:         }
  44:     }
  45:  
  46:     public static String GetWebResourceUrl(String resourceName)
  47:     {
  48:         Page page = HttpContext.Current.Handler as Page;
  49:  
  50:         if (page != null)
  51:         {
  52:             Type type = page.GetType();
  53:  
  54:             if (type.Namespace == "ASP")
  55:             {
  56:                 type = type.BaseType;
  57:             }
  58:  
  59:             return (page.ClientScript.GetWebResourceUrl(type, resourceName));
  60:         }
  61:  
  62:         return (String.Empty);
  63:     }
  64:  
  65:     public static String GetWebResourceUrl(String assemblyName, String resourceName)
  66:     {
  67:         Page page = HttpContext.Current.Handler as Page;
  68:  
  69:         if (page != null)
  70:         {
  71:             Assembly asm = Assembly.Load(assemblyName);
  72:  
  73:             if (asm != null)
  74:             {
  75:                 return (page.ClientScript.GetWebResourceUrl(asm.GetExportedTypes()[0], resourceName));
  76:             }
  77:         }
  78:  
  79:         return (String.Empty);
  80:     }
  81:  
  82:     public override Boolean SupportsEvaluate
  83:     {
  84:         get
  85:         {
  86:             return (true);
  87:         }
  88:     }
  89: }

A typical usage would be:

   1: <!-- this one implicitly references the current assembly -->
   2: <script src='&lt;asp:Literal runat="server" Text="<%$ WebResourceUrl:MyNamespace.SomeScript.js %>"/>
   1: ' type="text/javascript">
   1: </script>
   2:  
   3: <!-- while this one explicitly references another assembly -->
   4: <script src='<asp:Literal runat="server" Text="<%$ WebResourceUrl:AnotherAssembly, AnotherNamespace.SomeOtherScript.js %>"/>' type="text/javascript">
</script>
   3:  
   4: <asp:Image runat="server" ImageUrl='<%$ ThemeFileUrl:MyThemePicture.png %>' />
   5:  
   6:  

You will have to register these expression builders on the Web.config file:

   1:  <system.web>
   2:         <compilation>
   3:             <expressionBuilders>
   4:                 <add expressionPrefix="WebResourceUrl" type="MyNamespace.WebResourceUrlExpressionBuilder, MyAssembly"/>
   5:                 <add expressionPrefix="ThemeFileUrl" type="MyNamespace.ThemeFileUrlExpressionBuilder, MyAssembly"/>
   6:             </expressionBuilders>
   7:         </compilation>
   8: ...

And for the embedded resource, you mark a file (in this case, a JavaScript file) as an embedded resource, like this:

image

And you also have to register it with an assembly-level attribute, probably on AssemblyInfo.cs:

   1: [assembly: WebResource("MyNamespace.SomeScript.js", "text/javascript")]

Of course, do replace MyNamespace and MyAssembly for your actual values. By the way, this is not specific to .NET 4, it will work from 2.0 upwards.

Looking forward to hearing from you! Winking smile

Saving View State in Cache

Most people who have worked with ASP.NET web forms know about view state; they may have both benefited from and cursed it, since it allows simple things to be done very easily but also as easily doing very stupid things, like filling up an HTML page with several megabytes of data which have to be submitted on each postback.

What some people won’t know is that the way the view state is maintained is extensible and based on a provider model. Besides the default way of storing view state in an hidden field, ASP.NET includes another implementation of the provider which allows state to be stored in the session, thus keeping it out (but for a small part) of the HTML. The abstract base provider class is PageStatePersister and the two included implementations are HiddenFieldPageStatePersister and SessionPageStatePersister.

So, what keeps you from storing everything in the session? It seems like an easy choice… there are some drawbacks, however:

  • All browser tabs share the same session, so care must be taken if the same page is open on two tabs, because changes to the session made in one page will affect the other, since the data is shared between them;
  • Because data is kept indefinitely at the session, either until the session expires – which may never happen – or it is explicitly removed from it, memory will grow up enormously.

So I decided to go for another option: storing data on the cache, with an expiration value. This should be a reasonable default, after which, data would be gone, if no postback happens in the mean time. I made it configurable: a global default timeout may be specified on the Web.config file or specifically for a web Page in its Items collection. It is using sliding expiration, so each time the cache is touched is will keep it alive for an equal period of time.

One problem is how to identify uniquely a page instance, so that its view state is local to it only, but I have already solved some time ago.

OK, so let’s see the code:

   1: public class CachePageStatePersister : PageStatePersister
   2: {
   3:     private const String RequestId = "__REQUESTID";
   4:     private const String ViewStateId = "VIEWSTATE:{0}";
   5:     private const String ControlStateId = "CONTROLSTATE:{0}";
   6:     private const String CacheTimeoutMinutesKey = "CacheTimeoutMinutes";
   7:  
   8:     private const Int32 DefaultCacheTimeoutMinutes = 10;
   9:  
  10:     public CachePageStatePersister(Page page): base(page)
  11:     {
  12:  
  13:     }
  14:  
  15:     public override void Load()
  16:     {
  17:         String id = this.Page.ID;
  18:  
  19:         if ((String.IsNullOrWhiteSpace(id) == true) || (id == "__Page"))
  20:         {
  21:             id = this.Page.Request.Form[RequestId];
  22:         }
  23:  
  24:         if (String.IsNullOrWhiteSpace(id) == true)
  25:         {
  26:             throw (new InvalidOperationException("Missing page id"));
  27:         }
  28:  
  29:         this.Page.ID = id;
  30:  
  31:         this.Page.ClientScript.RegisterHiddenField(RequestId, id);
  32:  
  33:         String viewStateId = String.Format(ViewStateId, id);
  34:         String controlStateId = String.Format(ControlStateId, id);
  35:  
  36:         this.ViewState = this.Page.Cache[viewStateId];
  37:         this.ControlState = this.Page.Cache[controlStateId];
  38:     }
  39:  
  40:     public override void Save()
  41:     {
  42:         if ((this.ControlState != null) || (this.ViewState != null))
  43:         {
  44:             String id = this.Page.ID;
  45:  
  46:             if ((String.IsNullOrWhiteSpace(id) == true) || (id == "__Page"))
  47:             {
  48:                 id = Guid.NewGuid().ToString();
  49:  
  50:                 this.Page.ID = id;
  51:  
  52:                 this.Page.ClientScript.RegisterHiddenField(RequestId, id);
  53:             }
  54:  
  55:             Int32 cacheTimeoutMinutes = DefaultCacheTimeoutMinutes;
  56:  
  57:             if (String.IsNullOrWhiteSpace(ConfigurationManager.AppSettings[CacheTimeoutMinutesKey]) == false)
  58:             {
  59:                 Int32.TryParse(ConfigurationManager.AppSettings[CacheTimeoutMinutesKey], out cacheTimeoutMinutes);
  60:             }
  61:  
  62:             if (this.Page.Items[CacheTimeoutMinutesKey] is Int32)
  63:             {
  64:                 cacheTimeoutMinutes = (Int32)this.Page.Items[CacheTimeoutMinutesKey];
  65:             }
  66:  
  67:             String viewStateId = String.Format(ViewStateId, id);
  68:             String controlStateId = String.Format(ControlStateId, id);
  69:  
  70:             if (this.ViewState != null)
  71:             {
  72:                 this.Page.Cache.Add(viewStateId, this.ViewState, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(cacheTimeoutMinutes), System.Web.Caching.CacheItemPriority.Default, null);
  73:             }
  74:  
  75:             if (this.ControlState != null)
  76:             {
  77:                 this.Page.Cache.Add(controlStateId, this.ControlState, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(cacheTimeoutMinutes), System.Web.Caching.CacheItemPriority.Default, null);
  78:             }
  79:         }
  80:     }
  81: }

Finally, how to use this. There are two options:

   1: protected override PageStatePersister PageStatePersister
   2: {
   3:     get
   4:     {
   5:         return (new CachePageStatePersister(this));
   6:     }
   7: }
  • Or you can apply the persister through a control adapter, which is less intrusive, in two easy steps:

- Create a custom PageAdapter:

   1: public class CachePageAdapter : PageAdapter
   2: {
   3:     public override PageStatePersister GetStatePersister()
   4:     {
   5:         return (new CachePageStatePersister(this.Page));
   6:     }
   7: }

- Add a file named, for example, Default.browser, to the App_Browsers folder:

   1: <browsers>
   2:     <browser refID="Default">
   3:         <controlAdapters>
   4:             <adapter controlType="System.Web.UI.Page" adapterType="CachePageAdapter" />
   5:         </controlAdapters>
   6:     </browser>
   7: </browsers>

Of course, you can target page types other than the base Page itself by changing the value on the controlType attribute.

You can change the default cache timeout from 10 minutes to something else, globally in the Web.config:

   1: <configuration>
   2:     <appSettings>
   3:         <add key="CacheTimeoutMinutes" value="15"/>
   4:     </appSettings>
   5: ...
   6: </configuration>

Or by code, page by page:

   1: protected override void OnLoad(EventArgs e)
   2: {
   3:     this.Items["CacheTimeoutMinutes"] = 15;
   4:  
   5:     base.OnLoad(e);
   6: }
As always, hope this is useful, and looking forward to hearing your feedback!
O/RMs Dissected: My first article for Software Developer's Journal

My first article for Software Developer’s Journal came out on the SDJ Extra issue. You can download it from http://en.sdjournal.org/new-sdj-extra-0212-4/. I talk about what an O/RM is and why it is useful. Here’s the introduction:

Object/Relational Mapping, or O/RM, is a hot topic. Talk is everywhere, it seems that new O/RM libraries pop up every month – everyone is writing their own, and Microsoft itself is putting a considerable amount of its weight behind its own solution. So, what exactly is an O/RM for, and why should you care?

Looking forward to hearing your comments! Winking smile

Posted: Apr 01 2012, 09:51 AM by Ricardo Peres | with no comments
Filed under: ,
SharePoint 2010 Leiria

Ontem ocorreu o primeiro evento SharePoint 2010 Leiria, organizado pelo David Frazão, que me convidou, e no qual tive a honra de participar. Estavam presentes cerca de 12 pessoas, com experiências muito variadas de utilização do SharePoint. Foi muito interessante, sobretudo, se se vier a repetir!

Fiz uma breve apresentação sobre os Business Connectivity Services do SharePoint 2010. Podem fazer download dos slides e do código fonte no meu SkyDrive.

More Posts