File: Compilation\ResourceExpressionBuilder.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="ResourceExpressionBuilder.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.Compilation {
    using System;
    using System.Security.Permissions;
    using System.Collections;
    using System.Collections.Specialized;
    using System.CodeDom;
    using System.ComponentModel;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Resources;
    using System.Web.Caching;
    using System.Web.Compilation;
    using System.Web.Configuration;
    using System.Web.Hosting;
    using System.Web.Util;
    using System.Web.UI;
 
 
    [ExpressionPrefix("Resources")]
    [ExpressionEditor("System.Web.UI.Design.ResourceExpressionEditor, " + AssemblyRef.SystemDesign)]
    public class ResourceExpressionBuilder : ExpressionBuilder {
 
        private static ResourceProviderFactory s_resourceProviderFactory;
 
        public override bool SupportsEvaluate {
            get {
                return true;
            }
        }
 
        public static ResourceExpressionFields ParseExpression(string expression) {
            return ParseExpressionInternal(expression);
        }
 
        public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context) {
            ResourceExpressionFields fields = null;
 
            try {
                fields = ParseExpressionInternal(expression);
            }
            catch {
            }
 
            // If the parsing failed for any reason throw an error
            if (fields == null) {
                throw new HttpException(
                    SR.GetString(SR.Invalid_res_expr, expression));
            }
 
            // The resource expression was successfully parsed. We now need to check whether
            // the resource object actually exists in the neutral culture
 
            // If we don't have a virtual path, we can't check that the resource exists.
            // This happens in the designer.  
            if (context.VirtualPathObject != null) {
 
                IResourceProvider resourceProvider = GetResourceProvider(fields, VirtualPath.Create(context.VirtualPath));
                object o = null;
 
                if (resourceProvider != null) {
                    try {
                        o = resourceProvider.GetObject(fields.ResourceKey, CultureInfo.InvariantCulture);
                    }
                    catch {}
                }
 
                // If it doesn't throw an exception
                if (o == null) {
                    throw new HttpException(
                        SR.GetString(SR.Res_not_found, fields.ResourceKey));
                }
            }
 
            return fields;
        }
 
 
        public override CodeExpression GetCodeExpression(BoundPropertyEntry entry,
            object parsedData, ExpressionBuilderContext context) {
 
            // Retrieve our parsed data
            ResourceExpressionFields fields = (ResourceExpressionFields) parsedData;
 
            // If there is no classKey, it's a page-level resource
            if (fields.ClassKey.Length == 0) {
                return GetPageResCodeExpression(fields.ResourceKey, entry);
            }
 
            // Otherwise, it's a global resource
            return GetAppResCodeExpression(fields.ClassKey, fields.ResourceKey, entry);
        }
 
 
        public override object EvaluateExpression(object target, BoundPropertyEntry entry,
            object parsedData, ExpressionBuilderContext context) {
 
            // Retrieve our parsed data
            ResourceExpressionFields fields = (ResourceExpressionFields) parsedData;
 
            IResourceProvider resourceProvider = GetResourceProvider(fields, context.VirtualPathObject);
 
            if (entry.Type == typeof(string))
                return GetResourceObject(resourceProvider, fields.ResourceKey, null /*culture*/);
 
            // If the property is not of type string, pass in extra information
            // so that the resource value will be converted to the right type
            return GetResourceObject(resourceProvider, fields.ResourceKey, null /*culture*/,
                entry.DeclaringType, entry.PropertyInfo.Name);
        }
 
        private CodeExpression GetAppResCodeExpression(string classKey, string resourceKey, BoundPropertyEntry entry) {
            // We generate the following
            //      this.GetGlobalResourceObject(classKey)
            CodeMethodInvokeExpression expr = new CodeMethodInvokeExpression();
            expr.Method.TargetObject = new CodeThisReferenceExpression();
 
            expr.Method.MethodName = "GetGlobalResourceObject";
            expr.Parameters.Add(new CodePrimitiveExpression(classKey));
            expr.Parameters.Add(new CodePrimitiveExpression(resourceKey));
 
            // If the property is not of type string, it will need to be converted
            if (entry.Type != typeof(string) && entry.Type != null) {
                expr.Parameters.Add(new CodeTypeOfExpression(entry.DeclaringType));
                expr.Parameters.Add(new CodePrimitiveExpression(entry.PropertyInfo.Name));
            }
 
            return expr;
        }
 
        private CodeExpression GetPageResCodeExpression(string resourceKey, BoundPropertyEntry entry) {
            // We generate of the following
            //      this.GetLocalResourceObject(resourceKey)
            CodeMethodInvokeExpression expr = new CodeMethodInvokeExpression();
            expr.Method.TargetObject = new CodeThisReferenceExpression();
 
            expr.Method.MethodName = "GetLocalResourceObject";
            expr.Parameters.Add(new CodePrimitiveExpression(resourceKey));
 
            // If the property is not of type string, it will need to be converted
            if (entry.Type != typeof(string) && entry.Type != null) {
                expr.Parameters.Add(new CodeTypeOfExpression(entry.DeclaringType));
                expr.Parameters.Add(new CodePrimitiveExpression(entry.PropertyInfo.Name));
            }
 
            return expr;
        }
 
        internal static object GetGlobalResourceObject(string classKey, string resourceKey) {
            return GetGlobalResourceObject(classKey, resourceKey, null /*objType*/, null /*propName*/, null /*culture*/);
        }
 
        internal static object GetGlobalResourceObject(string classKey,
            string resourceKey, Type objType, string propName, CultureInfo culture) {
 
            IResourceProvider resourceProvider = GetGlobalResourceProvider(classKey);
            return GetResourceObject(resourceProvider, resourceKey, culture,
                objType, propName);
        }
 
        internal static object GetResourceObject(IResourceProvider resourceProvider,
            string resourceKey, CultureInfo culture) {
            return GetResourceObject(resourceProvider, resourceKey, culture,
                null /*objType*/, null /*propName*/);
        }
 
        internal static object GetResourceObject(IResourceProvider resourceProvider,
            string resourceKey, CultureInfo culture, Type objType, string propName) {
 
            if (resourceProvider == null)
                return null;
 
            object o = resourceProvider.GetObject(resourceKey, culture);
 
            // If no objType/propName was provided, return the object as is
            if (objType == null)
                return o;
 
            // Also, if the object from the resource is not a string, return it as is
            string s = o as String;
            if (s == null)
                return o;
 
            // If they were provided, perform the appropriate conversion
            return ObjectFromString(s, objType, propName);
        }
 
        private static object ObjectFromString(string value, Type objType, string propName) {
 
            // Get the PropertyDescriptor for the property
            PropertyDescriptor pd = TypeDescriptor.GetProperties(objType)[propName];
            Debug.Assert(pd != null);
            if (pd == null) return null;
 
            // Get its type descriptor
            TypeConverter converter = pd.Converter;
            Debug.Assert(converter != null);
            if (converter == null) return null;
 
            // Perform the conversion
            return converter.ConvertFromInvariantString(value);
        }
 
        // The following syntaxes are accepted for the expression
        //      resourceKey
        //      classKey, resourceKey
        //
        private static ResourceExpressionFields ParseExpressionInternal(string expression) {
            string classKey = null;
            string resourceKey = null;
 
            int len = expression.Length;
            if (len == 0) {
                return new ResourceExpressionFields(classKey, resourceKey);
            }
 
            // Split the comma separated string 
            string[] parts = expression.Split(',');
 
            int numOfParts = parts.Length;
 
            if (numOfParts > 2) return null;
 
            if (numOfParts == 1) {
                resourceKey = parts[0].Trim();
            }
            else {
                classKey = parts[0].Trim();
                resourceKey = parts[1].Trim();
            }
 
            return new ResourceExpressionFields(classKey, resourceKey);
        }
 
        private static IResourceProvider GetResourceProvider(ResourceExpressionFields fields,
            VirtualPath virtualPath) {
            // If there is no classKey, it's a page-level resource
            if (fields.ClassKey.Length == 0) {
                return GetLocalResourceProvider(virtualPath);
            }
 
            // Otherwise, it's a global resource
            return GetGlobalResourceProvider(fields.ClassKey);
        }
 
        private static void EnsureResourceProviderFactory() {
            if (s_resourceProviderFactory != null)
                return;
 
            Type t = null;
            GlobalizationSection globConfig = RuntimeConfig.GetAppConfig().Globalization;
            t = globConfig.ResourceProviderFactoryTypeInternal;
 
            // If we got a type from config, use it.  Otherwise, use default factory
            if (t == null) {
                s_resourceProviderFactory = new ResXResourceProviderFactory();
            }
            else {
                s_resourceProviderFactory = (ResourceProviderFactory) HttpRuntime.CreatePublicInstanceByWebObjectActivator(t);
            }
        }
 
        private static IResourceProvider GetGlobalResourceProvider(string classKey) {
            string fullClassName = BaseResourcesBuildProvider.DefaultResourcesNamespace +
                "." + classKey;
 
            // If we have it cached, return it
            CacheStoreProvider cacheInternal = System.Web.HttpRuntime.Cache.InternalCache;
            string cacheKey = CacheInternal.PrefixResourceProvider + fullClassName;
            IResourceProvider resourceProvider = cacheInternal.Get(cacheKey) as IResourceProvider;
            if (resourceProvider != null) {
                return resourceProvider;
            }
 
            EnsureResourceProviderFactory();
            resourceProvider = s_resourceProviderFactory.CreateGlobalResourceProvider(classKey);
 
            // Cache it
            cacheInternal.Insert(cacheKey, resourceProvider, null);
 
            return resourceProvider;
        }
 
        // Get the page-level IResourceProvider
        internal static IResourceProvider GetLocalResourceProvider(TemplateControl templateControl) {
            return GetLocalResourceProvider(templateControl.VirtualPath);
        }
 
        // Get the page-level IResourceProvider
        internal static IResourceProvider GetLocalResourceProvider(VirtualPath virtualPath) {
 
            // If we have it cached, return it (it may be null if there are no local resources)
            CacheStoreProvider cacheInternal = System.Web.HttpRuntime.Cache.InternalCache;
            string cacheKey = CacheInternal.PrefixResourceProvider + virtualPath.VirtualPathString;
            IResourceProvider resourceProvider = cacheInternal.Get(cacheKey) as IResourceProvider;
            if (resourceProvider != null) {
                return resourceProvider;
            }
 
            EnsureResourceProviderFactory();
            resourceProvider = s_resourceProviderFactory.CreateLocalResourceProvider(virtualPath.VirtualPathString);
 
            // Cache it
            cacheInternal.Insert(cacheKey, resourceProvider, null);
 
            return resourceProvider;
        }
 
        // Create a ResourceExpressionFields without actually parsing any string.  This
        // is used for 'automatic' resources, where we already have the pieces of
        // relevant data.
        internal static object GetParsedData(string resourceKey) {
            return new ResourceExpressionFields(String.Empty, resourceKey);
        }
    }
 
    // Holds the fields parsed from a resource expression (e.g. classKey, resourceKey)
    public sealed class ResourceExpressionFields {
        private string _classKey;
        private string _resourceKey;
 
        internal ResourceExpressionFields(string classKey, string resourceKey) {
            _classKey = classKey;
            _resourceKey = resourceKey;
        }
 
        public string ClassKey {
            get {
                if (_classKey == null) {
                    return String.Empty;
                }
 
                return _classKey;
            }
        }
 
        public string ResourceKey {
            get {
                if (_resourceKey == null) {
                    return String.Empty;
                }
 
                return _resourceKey;
            }
        }
    }
}