/UKVSTS/WebTestPlugins/UKVSTS.WebTestPlugins/ScriptedParamDetails.cs
# · C# · 323 lines · 185 code · 53 blank · 85 comment · 17 complexity · 28ba74ae936d93aab8202ca734a14293 MD5 · raw file
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- using Microsoft.VisualStudio.TestTools.WebTesting;
- using System.CodeDom;
- using Microsoft.CSharp;
- using System.IO;
- using System.CodeDom.Compiler;
-
- namespace UKVSTS.WebTestPlugins
- {
- class ScriptedParamDetails
- {
- /// <summary>
- /// The body of the script
- /// </summary>
- private String m_Code;
-
- /// <summary>
- /// The name of the parameter that is using this script
- /// </summary>
- private String m_ParamName;
-
- /// <summary>
- /// The compiled up script
- /// </summary>
- private MethodInfo m_Method;
-
- /// <summary>
- /// Use a delegate to store the address of the compiled up script.
- /// This will be quicker than calling it through reflection each time.
- /// </summary>
- private delegate object ScriptsGetValueDelegate (WebTestRequest request, WebTest test , WebTestContext context);
-
- /// <summary>
- /// The address of our compiled script.
- /// </summary>
- private ScriptsGetValueDelegate m_GetValueFunc;
-
- /// <summary>
- /// The namespace we will put our compiled script into.
- /// </summary>
- private const String GEN_NAMESPACE = "UKVSTS.WebTestPlugIns.Temp";
-
- /// <summary>
- /// The name for the method containing our script.
- /// </summary>
- private const String GEN_METHODNAME = "GetValue";
-
- /// <summary>
- /// Do we create a debug version of the code - this is to support dev of this plugin
- /// rather than support for the end user.
- /// </summary>
- private bool debug = true;
-
- /// <summary>
- /// List of namespaces that we will give import for the script block.
- /// </summary>
- private static String[] GEN_NAMESPACE_IMPORTS = new String [] {
- "System",
- "Microsoft.VisualStudio.TestTools.WebTesting"
- };
-
- /// <summary>
- /// Createas and compiles a script block
- /// </summary>
- /// <param name="paramName">The name of the param that is using this script</param>
- /// <param name="code">The body of this script</param>
- public ScriptedParamDetails(String paramName, String code)
- {
- debug = System.Diagnostics.Debugger.IsAttached;
- m_Code = code;
- m_ParamName = paramName;
-
- try
- {
- m_Method = CreateCodeAndReturnMethod(code, debug);
-
- // Create a delegate for this newly created method - this will be faster to
- // call the function than MethodInfo.Invoke
- // ..
- m_GetValueFunc = (ScriptsGetValueDelegate)Delegate.CreateDelegate(typeof(ScriptsGetValueDelegate), m_Method);
- }
- catch (Exception ex)
- {
- ScriptedParamException spx = ex as ScriptedParamException;
- if (spx == null)
- {
- spx = new ScriptedParamException(ex);
- }
- spx.ParamName = ParamName;
- throw;
- }
- }
-
- /// <summary>
- /// Uses the codedom surroung the script with a metod, class and namespace.
- /// </summary>
- /// <param name="code">The body of the script</param>
- /// <param name="debug">Flag to indicate if a debug version should be created</param>
- /// <returns>The MethodInfo object that reflects the compiled function</returns>
- static MethodInfo CreateCodeAndReturnMethod(String code, bool debug)
- {
- // Create the container where our code will be written.
- //
- CodeCompileUnit compileUnit = new CodeCompileUnit();
-
- // Create a namespace for our class and include and reference namespaces
- CodeNamespace ns = CreateNamespace(compileUnit);
-
- // Invent a unique name for this type.
- //
- String typeName = "ScriptClass" + Guid.NewGuid().ToString().Replace('-', '_');
-
- // Create the class definition
- //
- CodeTypeDeclaration td = CreateTypeDeclaration(ns, typeName);
-
- // Create the metod that contains our script.
- //
- td.Members.Add(CreateMethod(code));
-
- // Build it
- //
- Assembly assm = CompileCodeToAssembly(compileUnit, debug);
-
- // Find our method
- //
- Type t = assm.GetType(GEN_NAMESPACE + "." + typeName);
- return t.GetMethod(GEN_METHODNAME, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
- }
-
- static CodeMemberMethod CreateMethod (String body)
- {
- // Defines a method that returns a Object.
- //
- CodeMemberMethod method = new CodeMemberMethod();
- method.Name = GEN_METHODNAME;
- method.Attributes = MemberAttributes.Static | MemberAttributes.Public;
- method.ReturnType = new CodeTypeReference("System.Object");
-
- // Pass in a couple of useful args.
- //
- method.Parameters.Add(new CodeParameterDeclarationExpression("WebTestRequest", "Request"));
- method.Parameters.Add(new CodeParameterDeclarationExpression("WebTest", "WebTest"));
- method.Parameters.Add(new CodeParameterDeclarationExpression("WebTestContext", "Context"));
-
- // If the script is the form
- // <% = .....
- // then we want to subsitute a return statement for the =
- // If there is no equals, then we don't want to inser a return.
-
- bool needsReturn = false;
- int i = 0;
- int charsToTrim = 0;
- do
- {
- if (body[i] == '=')
- {
- needsReturn = true;
- charsToTrim = i + 1;
- break;
- }
-
- } while (Char.IsWhiteSpace(body[i++]));
-
- // Do we need to trim any chars of the string (i.e. white space and a =).
- //
- if (charsToTrim > 0)
- {
- body = body.Substring(charsToTrim);
- }
-
-
- if (needsReturn == true)
- {
-
- method.Statements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression(body)));
- }
- else
- {
- method.Statements.Add(new CodeSnippetExpression(body));
- }
-
- return method;
- }
-
-
- static CodeTypeDeclaration CreateTypeDeclaration(CodeNamespace ns, String typeName)
- {
- // Creates a new type declaration.
- CodeTypeDeclaration newType = new CodeTypeDeclaration(typeName);
-
- // Sets the member attributes for the type.
- newType.Attributes = MemberAttributes.Public | MemberAttributes.Static;
-
- ns.Types.Add(newType);
-
- return newType;
- }
-
- private static CodeNamespace CreateNamespace(CodeCompileUnit compileUnit)
- {
- // Declare a new namespace.
- //
- CodeNamespace ns = new CodeNamespace(GEN_NAMESPACE);
-
- // Add the new namespace to the compile unit.
- compileUnit.Namespaces.Add(ns);
-
- // Add the new namespace import for the System namespace.
- foreach (String import in GEN_NAMESPACE_IMPORTS)
- {
- ns.Imports.Add(new CodeNamespaceImport(import));
- }
-
- return ns;
- }
-
- public static Assembly CompileCodeToAssembly(CodeCompileUnit compileUnit, bool debug)
- {
- // Generate the code with the C# code provider.
- CSharpCodeProvider provider = new CSharpCodeProvider();
-
- TextWriter tw = null;
- try
- {
- if (debug)
- {
- StreamWriter sw = new StreamWriter(GEN_NAMESPACE + "." + compileUnit.Namespaces[0].Types[0].Name + ".cs");
- tw = new IndentedTextWriter(sw, " ");
- }
- else
- {
- // Use an in memory buffer to generate the code to.
- //
- tw = new StringWriter(new StringBuilder(1024));
- }
-
- // Generate source code using the code provider.
- //
- provider.GenerateCodeFromCompileUnit(compileUnit, tw, new CodeGeneratorOptions());
- }
- finally
- {
- if (tw != null)
- {
- tw.Close();
- }
- }
-
- CompilerParameters compilerParams = new CompilerParameters();
-
- // Need to ensure we have a reference to Microsoft.VisualStudio.QualityTools.WebTestFramework.dll.
- // However this isn't in the GAC - it is in memory, so find the location from the loaded assembly
- //
- compilerParams.ReferencedAssemblies.Add(typeof(WebTest).Assembly.Location);
- compilerParams.GenerateInMemory = !debug;
-
- compilerParams.IncludeDebugInformation = debug;
-
- CompilerResults results = provider.CompileAssemblyFromDom(compilerParams, compileUnit);
-
- String errString = String.Empty;
-
- foreach (CompilerError err in results.Errors)
- {
- if (debug)
- {
- System.Diagnostics.Debug.WriteLine(String.Format("{0}({1},{2}): {3} {4}: {5}",
- err.FileName,
- err.Line,
- err.Column,
- err.IsWarning ? "Warning" : "Error",
- err.ErrorNumber,
- err.ErrorText));
- }
- if (err.IsWarning == false)
- {
- errString += err.ErrorText + "\n";
- }
- }
-
- if (errString != String.Empty)
- {
- throw new ScriptedParamException("An error has occured when compiling a script: " + errString
- + ". Please ensure that the script is of the format <%= value %> or <% return value; %>");
- }
-
- return results.CompiledAssembly;
- }
-
-
- public String Code
- {
- get { return m_Code; }
- }
-
- public String ParamName
- {
- get { return m_ParamName; }
- }
-
- /// <summary>
- /// Executes the script and returns whatever the script evaluated.
- /// </summary>
- /// <param name="e"></param>
- /// <returns></returns>
- public String InvokeScript(PreRequestEventArgs e)
- {
- try
- {
- Object retval = m_GetValueFunc(e.Request, e.WebTest, e.WebTest.Context);
- return retval == null ? "(null)" : retval.ToString();
- }
- catch (Exception ex)
- {
- throw new ScriptedParamException("Script execution error \"" + ex.Message + "\"", ParamName, ex);
- }
- }
- }
- }