PageRenderTime 51ms CodeModel.GetById 28ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/Compilation/ReflectExpressionBuilder.cs

#
C# | 215 lines | 99 code | 19 blank | 97 comment | 11 complexity | faa15030f2734633ca760b688098d46a MD5 | raw file
  1// --------------------------------------------------------------------------------------------------------------------
  2// <summary>
  3//   Implements an expression builder that provides a strongly-typed reference to a type or member info, instead of a string.
  4//   Targeted properties still receive a string as input, but the expression is validated during page compilation or execution.
  5//   If the supplied expression cannot be resolved to a type or member info from an assembly loaded into the current domain
  6//   then an exception is thrown. Note that type and member name resolution is case-sensitive.
  7//   Only public types and members can be resolved.
  8// </summary>
  9// --------------------------------------------------------------------------------------------------------------------
 10
 11namespace BlogEngine.Core.Compilation
 12{
 13    using System;
 14    using System.CodeDom;
 15    using System.Linq;
 16    using System.Security.Permissions;
 17    using System.Web;
 18    using System.Web.Compilation;
 19    using System.Web.UI;
 20
 21    /// <summary>
 22    /// Implements an expression builder that provides a strongly-typed reference to a type or member info, instead of a string.
 23    ///     Targeted properties still receive a string as input, but the expression is validated during page compilation or execution.
 24    ///     If the supplied expression cannot be resolved to a type or member info from an assembly loaded into the current domain
 25    ///     then an exception is thrown. Note that type and member name resolution is case-sensitive.
 26    ///     Only public types and members can be resolved.
 27    /// </summary>
 28    /// <remarks>
 29    /// &lt;add expressionPrefix="Reflect" type="WebApp.Compilation.ReflectExpressionBuilder, WebApp"/&gt;
 30    ///     Add the above to the web.config.
 31    ///     The following examples illustrate the usage of the expression in web pages:
 32    ///     &lt;asp:ObjectDataSource SelectMethod="&lt;%$ Reflect: ComCard.Components.BusinessObjects.Task, Search %&gt;" ... /&gt;
 33    ///     This will resolve the type ComCard.Components.BusinessObjects.Task, and validate that Search is a public member on that type.
 34    ///     The member name will be returned for binding to the property.
 35    ///     &lt;asp:ObjectDataSource DataObjectTypeName="&lt;%$ Reflect: ComCard.Components.BusinessObjects.LogEntry %&gt;" ... /&gt;
 36    ///     This will resolve the type ComCard.Components.BusinessObjects.LogEntry. The type name will be returned for binding.
 37    /// </remarks>
 38    [ExpressionPrefix("Reflect")]
 39    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
 40    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
 41    public class ReflectExpressionBuilder : ExpressionBuilder
 42    {
 43        #region Properties
 44
 45        /// <summary>
 46        ///     Gets a flag that indicates whether the expression builder supports no-compile evaluation.
 47        ///     Returns true, as the target type can be validated at runtime as well.
 48        /// </summary>
 49        public override bool SupportsEvaluate
 50        {
 51            get
 52            {
 53                return true;
 54            }
 55        }
 56
 57        #endregion
 58
 59        #region Public Methods
 60
 61        /// <summary>
 62        /// Evaluates the expression at runtime.
 63        /// </summary>
 64        /// <param name="target">
 65        /// The target object.
 66        /// </param>
 67        /// <param name="entry">
 68        /// The entry for the property bound to the expression.
 69        /// </param>
 70        /// <param name="parsedData">
 71        /// The parsed expression data.
 72        /// </param>
 73        /// <param name="context">
 74        /// The current expression builder context.
 75        /// </param>
 76        /// <returns>
 77        /// A string representing the target type or member.
 78        /// </returns>
 79        public override object EvaluateExpression(
 80            object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
 81        {
 82            return parsedData;
 83        }
 84
 85        /// <summary>
 86        /// Returns a <see cref="System.CodeDom"/> expression for invoking the expression from a
 87        ///     compiled page at runtime.
 88        /// </summary>
 89        /// <param name="entry">
 90        /// The entry for the bound property.
 91        /// </param>
 92        /// <param name="parsedData">
 93        /// The parsed expression data.
 94        /// </param>
 95        /// <param name="context">
 96        /// The expression builder context.
 97        /// </param>
 98        /// <returns>
 99        /// A <see cref="CodeExpression"/> for invoking the expression.
100        /// </returns>
101        public override CodeExpression GetCodeExpression(
102            BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
103        {
104            return new CodePrimitiveExpression(parsedData);
105        }
106
107        /// <summary>
108        /// Parses and validates the expression data and returns a canonical type or member name, 
109        ///     or throws an exception if the expression is invalid.
110        /// </summary>
111        /// <param name="expression">
112        /// The raw expression to parse.
113        /// </param>
114        /// <param name="propertyType">
115        /// The target property type.
116        /// </param>
117        /// <param name="context">
118        /// Contextual information for the expression builder.
119        /// </param>
120        /// <returns>
121        /// A string representing the target type or member name for binding.
122        /// </returns>
123        public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context)
124        {
125            var parsed = false;
126            string typeName = null;
127            string memberName = null;
128
129            if (!String.IsNullOrEmpty(expression))
130            {
131                var parts = expression.Split(',');
132                if (parts.Length > 0 && parts.Length < 3)
133                {
134                    switch (parts.Length)
135                    {
136                        case 1:
137                            typeName = parts[0].Trim();
138                            break;
139                        case 2:
140                            typeName = parts[0].Trim();
141                            memberName = parts[1].Trim();
142                            break;
143                    }
144
145                    parsed = true;
146                }
147            }
148
149            if (!parsed)
150            {
151                throw new HttpException(String.Format("Invalid Reflect expression - '{0}'.", expression));
152            }
153
154            // now validate the expression fields
155            return ValidateExpression(typeName, memberName);
156        }
157
158        #endregion
159
160        #region Methods
161
162        /// <summary>
163        /// Validates that the specified type and member name can be resolved in the current context.
164        ///     Member name resolution is optional.
165        /// </summary>
166        /// <param name="typeName">
167        /// The full name of the type.
168        /// </param>
169        /// <param name="memberName">
170        /// The member name to resolve, or null to ignore.
171        /// </param>
172        /// <returns>
173        /// The type / member name as a string for binding to the target property.
174        /// </returns>
175        private static string ValidateExpression(string typeName, string memberName)
176        {
177            // resolve type name first
178            Type resolvedType = null;
179            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
180            {
181                resolvedType = assembly.GetType(typeName, false, false);
182                if (resolvedType != null)
183                {
184                    break;
185                }
186            }
187
188            // if type was not resolved then raise error
189            if (resolvedType == null)
190            {
191                var message =
192                    String.Format(
193                        "Reflect Expression: Type '{0}' could not be resolved in the current context.", typeName);
194                throw new HttpCompileException(message);
195            }
196
197            // resolve the member name if provided - don't care about multiple matches
198            var bindingValue = typeName;
199            if (!String.IsNullOrEmpty(memberName))
200            {
201                bindingValue = memberName;
202                if (!resolvedType.GetMember(memberName).Any())
203                {
204                    var message = String.Format(
205                        "Reflect Expression: Member '{0}' for type '{1}' does not exist.", memberName, typeName);
206                    throw new HttpCompileException(message);
207                }
208            }
209
210            return bindingValue;
211        }
212
213        #endregion
214    }
215}