Last active
April 11, 2016 15:25
-
-
Save pmunin/e04c2d6fc3d0c4e13c946f569c17f3b9 to your computer and use it in GitHub Desktop.
helpers for ASP.NET, ASP.NET MVC app server side testing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Runtime.Serialization; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| using System.Web; | |
| using System.Web.Hosting; | |
| using System.Web.Routing; | |
| /// <summary> | |
| /// Allow to run and communicate with ASP.NET application in separate app domain (memory is not shared). | |
| /// Required for UnitTests. Had to place it here, because it does not load test assembly otherwise. | |
| /// (http://stackoverflow.com/questions/1373100/how-to-add-folder-to-assembly-search-path-at-runtime-in-net | |
| /// alternative was either to create a copy script puting test assemblies in bin folder, or register test assembly in GAC) | |
| /// Taken from here: https://lostechies.com/chadmyers/2008/06/25/hosting-an-entire-asp-net-mvc-request-for-testing-purposes/ | |
| /// </summary> | |
| [System.Diagnostics.DebuggerStepThrough] | |
| public class WebAppHostHelper : MarshalByRefObject | |
| { | |
| public static WebAppHostHelper Create(string webAppDir) | |
| { | |
| ///More flexible way is here: http://weblog.west-wind.com/posts/2005/Jul/20/AspNET-Runtime-Hosting-Update | |
| var res = System.Web.Hosting.ApplicationHost.CreateApplicationHost(typeof(WebAppHostHelper), "/", webAppDir) as WebAppHostHelper; | |
| return res; | |
| } | |
| public WebAppHostHelper() | |
| { | |
| AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; | |
| } | |
| public void SetAssemblyLocations(params string[] binPaths) | |
| { | |
| this.binPaths = binPaths; | |
| } | |
| public void LoadAssemblies(params string[] assemblyFiles) | |
| { | |
| assemblyFiles.ToList().ForEach(a => { | |
| Assembly.LoadFrom(a); | |
| }); | |
| } | |
| string[] binPaths = null; | |
| private System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) | |
| { | |
| var lookingForFilename = new AssemblyName(args.Name).Name+".dll"; | |
| var dirs = (binPaths?? Enumerable.Empty<string>()).Select(bp => new DirectoryInfo(bp)); | |
| foreach (var dir in dirs) | |
| { | |
| var assemblyFile = dir.GetFiles().FirstOrDefault(f=>string.Equals(f.Name, lookingForFilename, StringComparison.InvariantCultureIgnoreCase)); | |
| if (assemblyFile != null) | |
| return Assembly.LoadFrom(assemblyFile.FullName); | |
| } | |
| return null; | |
| } | |
| public object CreateInstance(Type type, params object[] args) | |
| { | |
| return Activator.CreateInstance(type, args: args); | |
| } | |
| public AppDomain GetAppDomain() | |
| { | |
| return AppDomain.CurrentDomain; | |
| } | |
| public override object InitializeLifetimeService() | |
| { | |
| // This tells the CLR not to surreptitiously | |
| // destroy this object -- it's a singleton | |
| // and will live for the life of the appdomain | |
| return null; | |
| } | |
| /// <summary> | |
| /// Wake up ASP.NET | |
| /// </summary> | |
| /// <param name="relativeUrl"></param> | |
| /// <returns></returns> | |
| public string WakeUpAspNetMvc(string relativeUrl = null) | |
| { | |
| using (var response = new StringWriter()) | |
| { | |
| HttpRuntime.ProcessRequest(new SimpleWorkerRequest(relativeUrl ?? "/", "", response)); | |
| response.Flush(); | |
| return response.ToString(); | |
| } | |
| } | |
| public string ProcessRequest(string url, string query) | |
| { | |
| var writer = new StringWriter(); | |
| var request = new SimpleWorkerRequest(url, query, writer); | |
| var context = HttpContext.Current = new HttpContext(request); | |
| var contextBase = new HttpContextWrapper(context); | |
| var routeData = RouteTable.Routes.GetRouteData(contextBase); | |
| var routeHandler = routeData.RouteHandler; | |
| var requestContext = new RequestContext(contextBase, routeData); | |
| var httpHandler = routeHandler.GetHttpHandler(requestContext); | |
| httpHandler.ProcessRequest(context); | |
| context.Response.End(); | |
| writer.Flush(); | |
| return writer.GetStringBuilder().ToString(); | |
| } | |
| protected internal T Invoke<T>(SerializableDelegate<Func<WebAppHostHelper, T>> func) | |
| { | |
| if (func == null || func.Delegate==null) | |
| throw new SerializationException("Could not serialize your delegate for cross domain execution"); | |
| return func.Delegate(this); | |
| } | |
| protected internal void Invoke(SerializableDelegate<Action<WebAppHostHelper>> action) | |
| { | |
| if (action == null || action.Delegate == null) | |
| throw new SerializationException("Could not serialize your delegate for cross domain execution"); | |
| action.Delegate(this); | |
| } | |
| } | |
| [System.Diagnostics.DebuggerStepThrough] | |
| public static class WebAppHostHelperExtensions | |
| { | |
| public static T Invoke<T>(this WebAppHostHelper host, Func<WebAppHostHelper,T> crossDomainAction) | |
| { | |
| var serDel = new SerializableDelegate<Func<WebAppHostHelper, T>>(crossDomainAction); | |
| return host.Invoke(serDel); | |
| } | |
| public static void Invoke(this WebAppHostHelper host, Action<WebAppHostHelper> crossDomainAction) | |
| { | |
| var serDel = new SerializableDelegate<Action<WebAppHostHelper>>(crossDomainAction); | |
| host.Invoke(serDel); | |
| } | |
| } | |
| /// <summary> | |
| /// Makes delegates serializable where possible | |
| /// Taken from here: | |
| /// https://github.com/chrisortman/MvcIntegrationTest/blob/master/src/MvcIntegrationTestFramework/Hosting/SerializableDelegate.cs | |
| /// Used to pass test delegates from the test appdomain into the ASP.NET host appdomain | |
| /// Adapted from http://www.codeproject.com/KB/cs/AnonymousSerialization.aspx | |
| /// </summary> | |
| [Serializable] | |
| [System.Diagnostics.DebuggerStepThrough] | |
| public class SerializableDelegate<TDelegate> : ISerializable where TDelegate : class | |
| { | |
| public TDelegate Delegate { get; private set; } | |
| internal SerializableDelegate(TDelegate @delegate) | |
| { | |
| Delegate = @delegate; | |
| } | |
| internal SerializableDelegate(SerializationInfo info, StreamingContext context) | |
| { | |
| var delegateType = (Type)info.GetValue("delegateType", typeof(Type)); | |
| if (info.GetBoolean("isSerializable")) | |
| //If it's a "simple" delegate we just read it straight off | |
| Delegate = (TDelegate)info.GetValue("delegate", delegateType); | |
| else { | |
| //otherwise, we need to read its anonymous class | |
| var methodInfo = (MethodInfo)info.GetValue("method", typeof(MethodInfo)); | |
| var anonymousClassWrapper = (AnonymousClassWrapper)info.GetValue("class", typeof(AnonymousClassWrapper)); | |
| Delegate = (TDelegate)(object)System.Delegate.CreateDelegate(delegateType, anonymousClassWrapper.TargetInstance, methodInfo); | |
| } | |
| } | |
| void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) | |
| { | |
| info.AddValue("delegateType", Delegate.GetType()); | |
| var untypedDelegate = (Delegate)(object)Delegate; | |
| //If it's an "simple" delegate we can serialize it directly | |
| if ((untypedDelegate.Target == null || untypedDelegate.Method.DeclaringType.GetCustomAttributes(typeof(SerializableAttribute), false).Length > 0) && Delegate != null) | |
| { | |
| info.AddValue("isSerializable", true); | |
| info.AddValue("delegate", Delegate); | |
| } | |
| else { | |
| //otherwise, serialize anonymous class | |
| info.AddValue("isSerializable", false); | |
| info.AddValue("method", untypedDelegate.Method); | |
| info.AddValue("class", new AnonymousClassWrapper(untypedDelegate.Method.DeclaringType, untypedDelegate.Target)); | |
| } | |
| } | |
| [Serializable] | |
| private class AnonymousClassWrapper : ISerializable | |
| { | |
| public object TargetInstance { get; private set; } | |
| private readonly Type targetType; | |
| internal AnonymousClassWrapper(Type targetType, object targetInstance) | |
| { | |
| this.targetType = targetType; | |
| TargetInstance = targetInstance; | |
| } | |
| internal AnonymousClassWrapper(SerializationInfo info, StreamingContext context) | |
| { | |
| var classType = (Type)info.GetValue("classType", typeof(Type)); | |
| TargetInstance = Activator.CreateInstance(classType); | |
| foreach (FieldInfo field in classType.GetFields()) | |
| { | |
| if (typeof(Delegate).IsAssignableFrom(field.FieldType)) | |
| //If the field is a delegate | |
| field.SetValue(TargetInstance, ((SerializableDelegate<TDelegate>)info.GetValue(field.Name, typeof(SerializableDelegate<TDelegate>))).Delegate); | |
| else if (!field.FieldType.IsSerializable) | |
| //If the field is an anonymous class | |
| field.SetValue(TargetInstance, ((AnonymousClassWrapper)info.GetValue(field.Name, typeof(AnonymousClassWrapper))).TargetInstance); | |
| else | |
| //otherwise | |
| field.SetValue(TargetInstance, info.GetValue(field.Name, field.FieldType)); | |
| } | |
| } | |
| void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) | |
| { | |
| info.AddValue("classType", targetType); | |
| foreach (FieldInfo field in targetType.GetFields()) | |
| { | |
| //See corresponding comments above | |
| if (typeof(Delegate).IsAssignableFrom(field.FieldType)) | |
| info.AddValue(field.Name, new SerializableDelegate<TDelegate>((TDelegate)field.GetValue(TargetInstance))); | |
| else if (!field.FieldType.IsSerializable) | |
| info.AddValue(field.Name, new AnonymousClassWrapper(field.FieldType, field.GetValue(TargetInstance))); | |
| else | |
| info.AddValue(field.Name, field.GetValue(TargetInstance)); | |
| } | |
| } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment