e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
950 lines
37 KiB
C#
950 lines
37 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
|
|
namespace System.ServiceModel.Activation
|
|
{
|
|
using System.CodeDom;
|
|
using System.CodeDom.Compiler;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using System.Web;
|
|
using System.Web.Hosting;
|
|
using System.Web.Compilation;
|
|
using System.Web.RegularExpressions;
|
|
using System.ServiceModel.Activation.Diagnostics;
|
|
using System.Security;
|
|
using System.Runtime.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime;
|
|
|
|
/// <summary>
|
|
/// This class will parse the .svc file and maintains a list of useful information that the build
|
|
/// provider needs in order to compile the file. The parser creates a list of dependent assemblies,
|
|
/// understands the compiler that we need to use, fully parses all the supported directives etc.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The class is not thread-safe.
|
|
/// </remarks>
|
|
#pragma warning disable 618 // have not moved to the v4 security model yet
|
|
[SecurityCritical(SecurityCriticalScope.Everything)]
|
|
#pragma warning restore 618
|
|
class ServiceParser
|
|
{
|
|
// the delimiter for the compiled custom string
|
|
const string Delimiter = ServiceHostingEnvironment.ServiceParserDelimiter;
|
|
|
|
// attribute names
|
|
const string DefaultDirectiveName = "ServiceHost";
|
|
const string FactoryAttributeName = "Factory";
|
|
const string ServiceAttributeName = "Service";
|
|
|
|
// regular exression for the directive
|
|
readonly static SimpleDirectiveRegex directiveRegex;
|
|
|
|
// the build provider we will work with
|
|
ServiceBuildProvider buildProvider;
|
|
|
|
// text for the file
|
|
string serviceText;
|
|
|
|
// the class attribute value
|
|
string factoryAttributeValue = string.Empty;
|
|
|
|
// the constructorstring
|
|
string serviceAttributeValue = string.Empty;
|
|
|
|
// the line number in file currently being parsed
|
|
int lineNumber;
|
|
|
|
// the column number in file currently being parsed
|
|
int startColumn;
|
|
|
|
// the main directive was found or not
|
|
bool foundMainDirective;
|
|
|
|
// the type of the compiler (i.e C#)
|
|
CompilerType compilerType;
|
|
|
|
// the string containing the code to be compiled,
|
|
// it will be null when all the code is "behind"
|
|
string sourceString;
|
|
|
|
// assemblies to be linked with, we need a unique list
|
|
// of them and we maintain a Dictionary for it.
|
|
HybridDictionary linkedAssemblies;
|
|
|
|
// the set of assemblies that the build system is
|
|
// telling us we will be linked with. There is no unique
|
|
// requirement for them.
|
|
ICollection referencedAssemblies;
|
|
|
|
// used to figure out where the new lines start
|
|
static char[] newlineChars = new char[] { '\r', '\n' };
|
|
|
|
// source file dependencies
|
|
HybridDictionary sourceDependencies;
|
|
|
|
// virtual path for the file that we are parsing
|
|
string virtualPath;
|
|
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.AptcaMethodsShouldOnlyCallAptcaMethods, Justification = "Users cannot pass arbitrary data to this code.")]
|
|
static ServiceParser()
|
|
{
|
|
directiveRegex = new SimpleDirectiveRegex();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Contructor needs the path to the file that it will parse and a reference to
|
|
/// the build provider that we are using. This is necessary because there are things that
|
|
/// need to be set on the build provider directly as we are parsing...
|
|
/// </summary>
|
|
internal ServiceParser(string virtualPath, ServiceBuildProvider buildProvider)
|
|
{
|
|
if (DiagnosticUtility.ShouldTraceInformation)
|
|
{
|
|
TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.WebHostCompilation, SR.TraceCodeWebHostCompilation,
|
|
new StringTraceRecord("VirtualPath", virtualPath), this, (Exception)null);
|
|
}
|
|
|
|
this.virtualPath = virtualPath;
|
|
this.buildProvider = buildProvider;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor that is used when the whole svc file content is provided. This is the case
|
|
/// when the COM+ Admin tool calls into it.
|
|
/// </summary>
|
|
ServiceParser(string serviceText)
|
|
{
|
|
this.serviceText = serviceText;
|
|
this.buildProvider = new ServiceBuildProvider();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parsing the content of the service file and retrieve the serviceAttributeValue attribute for ComPlus.
|
|
/// </summary>
|
|
/// <param name="serviceText">The content of the service file.</param>
|
|
/// <returns>The "serviceAttributeValue" attribute of the Service directive. </returns>
|
|
/// <exception cref="System.Web.HttpParseException"/>
|
|
internal static IDictionary<string, string> ParseServiceDirective(string serviceText)
|
|
{
|
|
ServiceParser parser = new ServiceParser(serviceText);
|
|
parser.ParseString();
|
|
|
|
// the list of valid attributes for ComPlus for Service Directive
|
|
IDictionary<string, string> attributeTable = new Dictionary<string, string>(
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
if (!string.IsNullOrEmpty(parser.factoryAttributeValue))
|
|
attributeTable.Add(FactoryAttributeName, parser.factoryAttributeValue);
|
|
|
|
if (!string.IsNullOrEmpty(parser.serviceAttributeValue))
|
|
attributeTable.Add(ServiceAttributeName, parser.serviceAttributeValue);
|
|
|
|
return attributeTable;
|
|
}
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
|
|
// various getters for private objects that the build
|
|
// provider will need
|
|
//
|
|
internal CompilerType CompilerType
|
|
{
|
|
get
|
|
{
|
|
return compilerType;
|
|
}
|
|
}
|
|
|
|
internal ICollection AssemblyDependencies
|
|
{
|
|
get
|
|
{
|
|
if (linkedAssemblies == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return linkedAssemblies.Keys;
|
|
}
|
|
}
|
|
|
|
internal ICollection SourceDependencies
|
|
{
|
|
get
|
|
{
|
|
if (sourceDependencies == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return sourceDependencies.Keys;
|
|
}
|
|
}
|
|
|
|
internal bool HasInlineCode
|
|
{
|
|
get
|
|
{
|
|
return (sourceString != null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the code file appropriately. This method is used by the
|
|
/// build provider.
|
|
/// </summary>
|
|
internal void Parse(ICollection referencedAssemblies)
|
|
{
|
|
if (referencedAssemblies == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("referencedAssemblies");
|
|
}
|
|
|
|
this.referencedAssemblies = referencedAssemblies;
|
|
AddSourceDependency(virtualPath);
|
|
|
|
using (TextReader reader = buildProvider.OpenReaderInternal())
|
|
{
|
|
this.serviceText = reader.ReadToEnd();
|
|
ParseString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method returns a code compile unit that will be added
|
|
/// to the other depdnecies in order to compile
|
|
/// </summary>
|
|
internal CodeCompileUnit GetCodeModel()
|
|
{
|
|
// Do we have something to compile?
|
|
//
|
|
if (sourceString == null || sourceString.Length == 0)
|
|
return null;
|
|
|
|
CodeSnippetCompileUnit snippetCompileUnit = new CodeSnippetCompileUnit(sourceString);
|
|
|
|
// Put in some context so that the file can be debugged.
|
|
//
|
|
string pragmaFile = HostingEnvironmentWrapper.MapPath(virtualPath);
|
|
snippetCompileUnit.LinePragma = new CodeLinePragma(pragmaFile, lineNumber);
|
|
|
|
return snippetCompileUnit;
|
|
}
|
|
|
|
Exception CreateParseException(string message, string sourceCode)
|
|
{
|
|
return CreateParseException(message, null, sourceCode);
|
|
}
|
|
|
|
Exception CreateParseException(Exception innerException, string sourceCode)
|
|
{
|
|
return CreateParseException(innerException.Message, innerException, sourceCode);
|
|
}
|
|
|
|
Exception CreateParseException(string message, Exception innerException, string sourceCode)
|
|
{
|
|
return new HttpParseException(message, innerException, this.virtualPath, sourceCode, this.lineNumber);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method returns the custom string that is to be passed to ServiceHostingEnvironment from BuildManager.
|
|
/// </summary>
|
|
/// <param name="compiledAssembly">The full name of the built assembly for inline code.</param>
|
|
internal string CreateParseString(Assembly compiledAssembly)
|
|
{
|
|
Type typeToPreserve = this.GetCompiledType(compiledAssembly);
|
|
string typeToPreserveName = string.Empty;
|
|
if (typeToPreserve != null)
|
|
typeToPreserveName = typeToPreserve.AssemblyQualifiedName;
|
|
|
|
System.Text.StringBuilder builder = new System.Text.StringBuilder();
|
|
if (compiledAssembly != null)
|
|
{
|
|
builder.Append(Delimiter);
|
|
builder.Append(compiledAssembly.FullName);
|
|
}
|
|
|
|
if (this.referencedAssemblies != null)
|
|
{
|
|
// CSDMain #192135
|
|
// Minimize code change by doing 2 passes to have assembly containing type at the top of the list.
|
|
// As a result, this assembly will get loaded first in ServiceHostFactory.CreateServiceHost.
|
|
// In the multi-targetting scenario this prevents the runtime from trying to load a newer CLR assembly
|
|
// and failing. In the happy case, duplicate assembly references may occur (no effect on runtime).
|
|
// Note that if the service type is contained in a framework assembly, this does not fix the problem.
|
|
// Future improvement is to write fully qualified type name and let CLR handle load/search.
|
|
if (!string.IsNullOrEmpty(serviceAttributeValue))
|
|
{
|
|
foreach (Assembly assembly in this.referencedAssemblies)
|
|
{
|
|
Type serviceType;
|
|
try
|
|
{
|
|
serviceType = assembly.GetType(serviceAttributeValue, false);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
// log exception, but do not rethrow
|
|
DiagnosticUtility.TraceHandledException(e, TraceEventType.Warning);
|
|
|
|
break;
|
|
}
|
|
|
|
if (serviceType != null)
|
|
{
|
|
builder.Append(Delimiter);
|
|
builder.Append(assembly.FullName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (Assembly assembly in this.referencedAssemblies)
|
|
{
|
|
builder.Append(Delimiter);
|
|
builder.Append(assembly.FullName);
|
|
}
|
|
}
|
|
|
|
if (this.AssemblyDependencies != null)
|
|
{
|
|
foreach (Assembly assembly in this.AssemblyDependencies)
|
|
{
|
|
builder.Append(Delimiter);
|
|
builder.Append(assembly.FullName);
|
|
}
|
|
}
|
|
// use application relative virtualpath instead of the absolute path
|
|
// so that the compliedcustomstring is applicationame independent
|
|
return string.Concat(VirtualPathUtility.ToAppRelative(virtualPath), Delimiter,
|
|
typeToPreserveName, Delimiter,
|
|
serviceAttributeValue, builder.ToString());
|
|
}
|
|
|
|
void AddSourceDependency(string fileName)
|
|
{
|
|
if (sourceDependencies == null)
|
|
sourceDependencies = new HybridDictionary(true);
|
|
|
|
sourceDependencies.Add(fileName, fileName);
|
|
}
|
|
|
|
Type GetCompiledType(Assembly compiledAssembly)
|
|
{
|
|
if (string.IsNullOrEmpty(factoryAttributeValue))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type type = null;
|
|
|
|
// First, try to get the type from the assembly that has been built (if any)
|
|
if (this.HasInlineCode && (compiledAssembly != null))
|
|
{
|
|
type = compiledAssembly.GetType(factoryAttributeValue);
|
|
}
|
|
|
|
// If not, try to get it from other assemblies
|
|
if (type == null)
|
|
{
|
|
type = GetType(factoryAttributeValue);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
internal IDictionary GetLinePragmasTable()
|
|
{
|
|
LinePragmaCodeInfo info = new LinePragmaCodeInfo(this.lineNumber, this.startColumn, 1, -1, false);
|
|
IDictionary dictionary = new Hashtable();
|
|
dictionary[this.lineNumber] = info;
|
|
return dictionary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the content of the svc file for each directive line
|
|
/// </summary>
|
|
void ParseString()
|
|
{
|
|
try
|
|
{
|
|
int textPos = 0;
|
|
Match match;
|
|
lineNumber = 1;
|
|
|
|
// Check for ending bracket first, MB 45013.
|
|
if (this.serviceText.IndexOf('>') == -1)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderDirectiveEndBracketMissing(ServiceParser.DefaultDirectiveName)));
|
|
}
|
|
|
|
// First, parse all the <%@ ... %> directives
|
|
//
|
|
for (;;)
|
|
{
|
|
match = directiveRegex.Match(this.serviceText, textPos);
|
|
|
|
// Done with the directives?
|
|
//
|
|
if (!match.Success)
|
|
break;
|
|
|
|
lineNumber += ServiceParserUtilities.LineCount(this.serviceText, textPos, match.Index);
|
|
textPos = match.Index;
|
|
|
|
// Get all the directives into a bag
|
|
//
|
|
IDictionary directive = CollectionsUtil.CreateCaseInsensitiveSortedList();
|
|
string directiveName = ProcessAttributes(match, directive);
|
|
|
|
// Understand the directive
|
|
//
|
|
ProcessDirective(directiveName, directive);
|
|
lineNumber += ServiceParserUtilities.LineCount(this.serviceText, textPos, match.Index + match.Length);
|
|
textPos = match.Index + match.Length;
|
|
|
|
// Fixup line and column numbers to have meaninglful errors
|
|
//
|
|
int newlineIndex = this.serviceText.LastIndexOfAny(newlineChars, textPos - 1);
|
|
startColumn = textPos - newlineIndex;
|
|
}
|
|
|
|
if (!foundMainDirective)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderDirectiveMissing(ServiceParser.DefaultDirectiveName)));
|
|
}
|
|
|
|
// skip the directives chunk
|
|
//
|
|
string remainingText = this.serviceText.Substring(textPos);
|
|
|
|
// If there is something else in the file, it needs to be compiled
|
|
//
|
|
if (!ServiceParserUtilities.IsWhiteSpaceString(remainingText))
|
|
{
|
|
sourceString = remainingText;
|
|
}
|
|
}
|
|
catch (HttpException e)
|
|
{
|
|
// the string is set in the internal exception, no need to set it again.
|
|
//
|
|
Exception parseException = CreateParseException(e, this.serviceText);
|
|
throw FxTrace.Exception.AsError(
|
|
new HttpCompileException(parseException.Message, parseException));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the directive if it exists or an empty string
|
|
/// </summary>
|
|
string ProcessAttributes(Match match, IDictionary attribs)
|
|
{
|
|
// creates 3 parallel capture collections
|
|
// for the attribute names, the attribute values and the
|
|
// equal signs
|
|
//
|
|
string ret = String.Empty;
|
|
|
|
CaptureCollection attrnames = match.Groups["attrname"].Captures;
|
|
CaptureCollection attrvalues = match.Groups["attrval"].Captures;
|
|
CaptureCollection equalsign = match.Groups["equal"].Captures;
|
|
|
|
// Iterate through all of them and add then to
|
|
// the dictionary of attributes
|
|
//
|
|
for (int i = 0; i < attrnames.Count; i++)
|
|
{
|
|
string attribName = attrnames[i].ToString();
|
|
string attribValue = attrvalues[i].ToString();
|
|
|
|
// Check if there is an equal sign.
|
|
//
|
|
bool fHasEqual = (equalsign[i].ToString().Length > 0);
|
|
|
|
if (attribName != null)
|
|
{
|
|
// A <%@ %> block can have two formats:
|
|
// <%@ directive foo=1 bar=hello %>
|
|
// <%@ foo=1 bar=hello %>
|
|
// Check if we have the first format
|
|
//
|
|
if (!fHasEqual && i == 0)
|
|
{
|
|
// return the main directive
|
|
//
|
|
ret = attribName;
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (attribs != null)
|
|
attribs.Add(attribName, attribValue);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderDuplicateAttribute(attribName)));
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method understands the compilation parameters if any ...
|
|
/// </summary>
|
|
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.DoNotIndirectlyExposeMethodsWithLinkDemands, Justification = "This method doesn't allow callers to access sensitive information, operations, or resources that can be used in a destructive manner.")]
|
|
void ProcessCompilationParams(IDictionary directive, CompilerParameters compilParams)
|
|
{
|
|
bool debug = false;
|
|
if (ServiceParserUtilities.GetAndRemoveBooleanAttribute(directive, "debug", ref debug))
|
|
{
|
|
compilParams.IncludeDebugInformation = debug;
|
|
}
|
|
|
|
int warningLevel = 0;
|
|
if (ServiceParserUtilities.GetAndRemoveNonNegativeIntegerAttribute(directive, "warninglevel", ref warningLevel))
|
|
{
|
|
compilParams.WarningLevel = warningLevel;
|
|
if (warningLevel > 0)
|
|
compilParams.TreatWarningsAsErrors = true;
|
|
}
|
|
|
|
string compilerOptions = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, "compileroptions");
|
|
if (compilerOptions != null)
|
|
{
|
|
compilParams.CompilerOptions = compilerOptions;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Processes a directive block
|
|
/// </summary>
|
|
void ProcessDirective(string directiveName, IDictionary directive)
|
|
{
|
|
// Throw on empy, no directive specified
|
|
//
|
|
if (directiveName.Length == 0)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderDirectiveNameMissing));
|
|
}
|
|
|
|
// Check for the main directive
|
|
//
|
|
if (string.Compare(directiveName, ServiceParser.DefaultDirectiveName, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
// Make sure the main directive was not already specified
|
|
//
|
|
if (foundMainDirective)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderDuplicateDirective(ServiceParser.DefaultDirectiveName)));
|
|
}
|
|
|
|
foundMainDirective = true;
|
|
|
|
// Ignore 'codebehind' attribute (ASURT 4591)
|
|
//
|
|
directive.Remove("codebehind");
|
|
|
|
string language = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, "language");
|
|
|
|
// Get the compiler for the specified language (if any)
|
|
// or get the one from config
|
|
//
|
|
if (language != null)
|
|
{
|
|
compilerType = buildProvider.GetDefaultCompilerTypeForLanguageInternal(language);
|
|
}
|
|
else
|
|
{
|
|
compilerType = buildProvider.GetDefaultCompilerTypeInternal();
|
|
}
|
|
|
|
|
|
if (directive.Contains(FactoryAttributeName))
|
|
{
|
|
factoryAttributeValue = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, FactoryAttributeName);
|
|
serviceAttributeValue = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, ServiceAttributeName);
|
|
}
|
|
else if (directive.Contains(ServiceAttributeName))
|
|
{
|
|
serviceAttributeValue = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, ServiceAttributeName);
|
|
}
|
|
else
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderMainAttributeMissing));
|
|
}
|
|
// parse the parameters that are related to the compiler
|
|
//
|
|
ProcessCompilationParams(directive, compilerType.CompilerParameters);
|
|
}
|
|
else if (string.Compare(directiveName, "assembly", StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
if (directive.Contains("name") && directive.Contains("src"))
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderMutualExclusiveAttributes("src", "name")));
|
|
}
|
|
else if (directive.Contains("name"))
|
|
{
|
|
string assemblyName = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, "name");
|
|
if (assemblyName != null)
|
|
{
|
|
AddAssemblyDependency(assemblyName);
|
|
}
|
|
else
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderAttributeEmpty("name")));
|
|
}
|
|
else if (directive.Contains("src"))
|
|
{
|
|
string srcPath = ServiceParserUtilities.GetAndRemoveNonEmptyAttribute(directive, "src");
|
|
if (srcPath != null)
|
|
{
|
|
ImportSourceFile(srcPath);
|
|
}
|
|
else
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderAttributeEmpty("src")));
|
|
}
|
|
else
|
|
{ // if (!directive.Contains("name") && !directive.Contains("src"))
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderRequiredAttributesMissing("src", "name")));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderUnknownDirective(directiveName)));
|
|
}
|
|
|
|
// check if there are any directives that you did not process
|
|
//
|
|
if (directive.Count > 0)
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderUnknownAttribute(ServiceParserUtilities.FirstDictionaryKey(directive))));
|
|
}
|
|
|
|
void ImportSourceFile(string path)
|
|
{
|
|
// Get a full path to the source file, compile it to an assembly
|
|
// add the depedency to the assembly
|
|
//
|
|
string baseVirtualDir = VirtualPathUtility.GetDirectory(virtualPath);
|
|
string fullVirtualPath = VirtualPathUtility.Combine(baseVirtualDir, path);
|
|
|
|
AddSourceDependency(fullVirtualPath);
|
|
Assembly a = BuildManager.GetCompiledAssembly(fullVirtualPath);
|
|
AddAssemblyDependency(a);
|
|
}
|
|
|
|
void AddAssemblyDependency(string assemblyName)
|
|
{
|
|
// Load and keep track of the assembly
|
|
//
|
|
Assembly a = Assembly.Load(assemblyName);
|
|
AddAssemblyDependency(a);
|
|
}
|
|
|
|
void AddAssemblyDependency(Assembly assembly)
|
|
{
|
|
if (linkedAssemblies == null)
|
|
linkedAssemblies = new HybridDictionary(false);
|
|
|
|
linkedAssemblies.Add(assembly, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Look for a type by name in the assemblies available to this page
|
|
/// </summary>
|
|
Type GetType(string typeName)
|
|
{
|
|
Type type;
|
|
|
|
// If it contains an assembly name, just call Type.GetType (ASURT 53589)
|
|
//
|
|
if (ServiceParserUtilities.TypeNameIncludesAssembly(typeName))
|
|
{
|
|
try
|
|
{
|
|
type = Type.GetType(typeName, true);
|
|
}
|
|
catch (ArgumentException e)
|
|
{
|
|
Exception parseException = CreateParseException(e, this.sourceString);
|
|
throw FxTrace.Exception.AsError(
|
|
new HttpCompileException(parseException.Message, parseException));
|
|
}
|
|
catch (TargetInvocationException e)
|
|
{
|
|
Exception parseException = CreateParseException(e, this.sourceString);
|
|
throw FxTrace.Exception.AsError(
|
|
new HttpCompileException(parseException.Message, parseException));
|
|
}
|
|
catch (TypeLoadException e)
|
|
{
|
|
Exception parseException = CreateParseException(SR.Hosting_BuildProviderCouldNotCreateType(typeName), e, this.sourceString);
|
|
throw FxTrace.Exception.AsError(
|
|
new HttpCompileException(parseException.Message, parseException));
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
try
|
|
{
|
|
type = ServiceParserUtilities.GetTypeFromAssemblies(referencedAssemblies, typeName, false /*ignoreCase*/);
|
|
if (type != null)
|
|
return type;
|
|
|
|
type = ServiceParserUtilities.GetTypeFromAssemblies(AssemblyDependencies, typeName, false /*ignoreCase*/);
|
|
if (type != null)
|
|
return type;
|
|
}
|
|
catch (HttpException e)
|
|
{
|
|
Exception parseException = CreateParseException(SR.Hosting_BuildProviderCouldNotCreateType(typeName), e, this.sourceString);
|
|
throw FxTrace.Exception.AsError(
|
|
new HttpCompileException(parseException.Message, parseException));
|
|
}
|
|
|
|
Exception exception = CreateParseException(SR.Hosting_BuildProviderCouldNotCreateType(typeName), this.sourceString);
|
|
throw FxTrace.Exception.AsError(
|
|
new HttpCompileException(exception.Message, exception));
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class contains static methods that are necessary to manipulate the
|
|
/// structures that contain the directives. The logic assumes that the parser will
|
|
/// create a dictionary that contains all the directives and we can pull certain directives as
|
|
/// necessary while processing/compiling the page. The directives are strings.
|
|
///
|
|
/// </summary>
|
|
static class ServiceParserUtilities
|
|
{
|
|
/// <summary>
|
|
/// Return the first key of the dictionary as a string. Throws if it's
|
|
/// empty or if the key is not a string.
|
|
/// </summary>
|
|
internal static string FirstDictionaryKey(IDictionary dictionary)
|
|
{
|
|
// assume that the caller has checked the dictionary before calling
|
|
//
|
|
IDictionaryEnumerator e = dictionary.GetEnumerator();
|
|
e.MoveNext();
|
|
return (string)e.Key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a string value from a dictionary, and remove
|
|
/// it from the dictionary of attributes if it exists.
|
|
/// </summary>
|
|
/// <remarks>Returns null if the value was not there ...</remarks>
|
|
static string GetAndRemove(IDictionary dictionary, string key)
|
|
{
|
|
string val = (string)dictionary[key];
|
|
|
|
if (val != null)
|
|
{
|
|
dictionary.Remove(key);
|
|
val = val.Trim();
|
|
}
|
|
else
|
|
return string.Empty;
|
|
|
|
return val;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a value from a dictionary, and remove it from the dictionary if
|
|
/// it exists. Throw an exception if the value is a whitespace string.
|
|
/// However, don't complain about null, which simply means the value is not
|
|
/// in the dictionary.
|
|
/// </summary>
|
|
internal static string GetAndRemoveNonEmptyAttribute(IDictionary directives, string key, bool required)
|
|
{
|
|
string val = ServiceParserUtilities.GetAndRemove(directives, key);
|
|
|
|
if (val.Length == 0)
|
|
{
|
|
if (required)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderAttributeMissing(key)));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
internal static string GetAndRemoveNonEmptyAttribute(IDictionary directives, string key)
|
|
{
|
|
return GetAndRemoveNonEmptyAttribute(directives, key, false /*required*/);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a string value from a dictionary, and convert it to bool. Throw an
|
|
/// exception if it's not a valid bool string.
|
|
/// However, don't complain about null, which simply means the value is not
|
|
/// in the dictionary.
|
|
/// The value is returned through a REF param (unchanged if null)
|
|
/// </summary>
|
|
/// <returns>True if attrib exists, false otherwise</returns>
|
|
internal static bool GetAndRemoveBooleanAttribute(IDictionary directives, string key, ref bool val)
|
|
{
|
|
string s = ServiceParserUtilities.GetAndRemove(directives, key);
|
|
|
|
if (s.Length == 0)
|
|
return false;
|
|
|
|
try
|
|
{
|
|
val = bool.Parse(s);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderInvalidValueForBooleanAttribute(s, key)));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a string value from a dictionary, and convert it to integer. Throw an
|
|
/// exception if it's not a valid positive integer string.
|
|
/// However, don't complain about null, which simply means the value is not
|
|
/// in the dictionary.
|
|
/// The value is returned through a REF param (unchanged if null)
|
|
/// </summary>
|
|
/// <returns>True if attrib exists, false otherwise</returns>
|
|
internal static bool GetAndRemoveNonNegativeIntegerAttribute(IDictionary directives, string key, ref int val)
|
|
{
|
|
string s = ServiceParserUtilities.GetAndRemove(directives, key);
|
|
|
|
if (s.Length == 0)
|
|
return false;
|
|
|
|
val = GetNonNegativeIntegerAttribute(key, s);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string attribute into a non-negative integer
|
|
/// </summary>
|
|
/// <param name="name">Name of the attribute, used only for the error messages</param>
|
|
/// <param name="value">Value to convert to int</param>
|
|
static int GetNonNegativeIntegerAttribute(string name, string value)
|
|
{
|
|
int ret;
|
|
|
|
try
|
|
{
|
|
ret = int.Parse(value, CultureInfo.InvariantCulture);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderInvalidValueForNonNegativeIntegerAttribute(value, name)));
|
|
}
|
|
|
|
// Make sure it's not negative
|
|
//
|
|
if (ret < 0)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(SR.Hosting_BuildProviderInvalidValueForNonNegativeIntegerAttribute(value, name)));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
internal static bool IsWhiteSpaceString(string s)
|
|
{
|
|
return (s.Trim().Length == 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method takes the code that will be compiled as a string and it
|
|
/// will count how many lines exist between the given offset and the final
|
|
/// offset.
|
|
/// </summary>
|
|
/// <param name="text">The text that contains the source code</param>
|
|
/// <param name="offset">Starting offset for lookup</param>
|
|
/// <param name="newoffset">Ending offset</param>
|
|
/// <returns>The number of lines</returns>
|
|
internal static int LineCount(string text, int offset, int newoffset)
|
|
{
|
|
int linecount = 0;
|
|
|
|
while (offset < newoffset)
|
|
{
|
|
if (text[offset] == '\r' || (text[offset] == '\n' && (offset == 0 || text[offset - 1] != '\r')))
|
|
linecount++;
|
|
|
|
offset++;
|
|
}
|
|
|
|
return linecount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a string that contains a type trying to figure out if the assembly info is there.
|
|
/// </summary>
|
|
/// <param name="typeName">The string to search</param>
|
|
internal static bool TypeNameIncludesAssembly(string typeName)
|
|
{
|
|
return (typeName.IndexOf(",", StringComparison.Ordinal) >= 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loops through a list of assemblies that are already collected by the parser/provider and
|
|
/// looks for the specified type.
|
|
/// </summary>
|
|
/// <param name="assemblies">The collection of assemblies</param>
|
|
/// <param name="typeName">The type name</param>
|
|
/// <param name="ignoreCase">Case sensitivity knob</param>
|
|
/// <returns></returns>
|
|
internal static Type GetTypeFromAssemblies(ICollection assemblies, string typeName, bool ignoreCase)
|
|
{
|
|
if (assemblies == null)
|
|
return null;
|
|
|
|
Type type = null;
|
|
|
|
foreach (Assembly assembly in assemblies)
|
|
{
|
|
Type t = assembly.GetType(typeName, false /*throwOnError*/, ignoreCase);
|
|
|
|
if (t == null)
|
|
continue;
|
|
|
|
// If we had already found a different one, it's an ambiguous type reference
|
|
//
|
|
if (type != null && t != type)
|
|
{
|
|
throw FxTrace.Exception.AsError(new HttpException(
|
|
SR.Hosting_BuildProviderAmbiguousType(typeName, type.Assembly.FullName, t.Assembly.FullName)));
|
|
}
|
|
|
|
// Keep track of it
|
|
//
|
|
type = t;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
}
|
|
}
|
|
}
|