From 390a93fbed6b23ac201b4c7e7e20531920e9783b Mon Sep 17 00:00:00 2001 From: jgauffin Date: Mon, 29 Jun 2015 14:46:59 +0200 Subject: [PATCH 1/4] Made it easier to configure each faked part (by exposing all properties with setters). Also added fluent builders. --- src/Web/FakeHttpApplication.cs | 73 +++++++ src/Web/FakeHttpContext.cs | 220 +++++++++++++-------- src/Web/FakeHttpContextBuilder.cs | 149 +++++++++++++++ src/Web/FakeHttpContextControl.cs | 82 ++++++++ src/Web/FakeHttpRequest.cs | 142 +++++--------- src/Web/FakeHttpRequestBuilder.cs | 102 ++++++++++ src/Web/FakeHttpRequestControl.cs | 107 +++++++++++ src/Web/FakeHttpResponse.cs | 250 +++++++++++++++--------- src/Web/FakeHttpResponseBuilder.cs | 78 ++++++++ src/Web/FakeHttpResponseControl.cs | 71 +++++++ src/Web/FakeHttpServerUtilityBase.cs | 109 +++++++++++ src/Web/FakeHttpSessionState.cs | 16 +- src/Web/FakeHttpStateBase.cs | 273 +++++++++++++++++++++++++++ src/Web/FakeN.Web.csproj | 13 ++ src/Web/FakeRequestContext.cs | 37 ++++ src/Web/Helper.cs | 37 ++++ src/Web/HttpObjectExtensions.cs | 14 ++ src/Web/IHttpObject.cs | 4 + src/Web/TransferRequest.cs | 42 +++++ test/Web/FakeN.Web.Test.csproj | 1 + test/Web/HttpContextBuilderTests.cs | 62 ++++++ test/Web/SessionTests.cs | 6 +- 22 files changed, 1624 insertions(+), 264 deletions(-) create mode 100644 src/Web/FakeHttpApplication.cs create mode 100644 src/Web/FakeHttpContextBuilder.cs create mode 100644 src/Web/FakeHttpContextControl.cs create mode 100644 src/Web/FakeHttpRequestBuilder.cs create mode 100644 src/Web/FakeHttpRequestControl.cs create mode 100644 src/Web/FakeHttpResponseBuilder.cs create mode 100644 src/Web/FakeHttpResponseControl.cs create mode 100644 src/Web/FakeHttpServerUtilityBase.cs create mode 100644 src/Web/FakeHttpStateBase.cs create mode 100644 src/Web/FakeRequestContext.cs create mode 100644 src/Web/Helper.cs create mode 100644 src/Web/IHttpObject.cs create mode 100644 src/Web/TransferRequest.cs create mode 100644 test/Web/HttpContextBuilderTests.cs diff --git a/src/Web/FakeHttpApplication.cs b/src/Web/FakeHttpApplication.cs new file mode 100644 index 0000000..f18ac36 --- /dev/null +++ b/src/Web/FakeHttpApplication.cs @@ -0,0 +1,73 @@ +using System; +using System.Web; + +namespace FakeN.Web +{ + public class FakeHttpApplication : HttpApplication + { + /// + /// Used to override GetOutputCacheProviderName + /// + public Func GetOutputCacheProviderNameCallback; + + /// + /// Used to ovveride GetVaryByCustomString. + /// + public Func GetVaryByCustomStringCallback; + + public FakeHttpApplication(HttpApplication assignedApplication) + { + AssignedApplication = assignedApplication; + } + + public FakeHttpApplication() + + { + } + + public HttpApplication AssignedApplication { get; set; } + + /// + /// Gets the name of the default output-cache provider that is configured for a Web site. + /// + /// + /// The name of the default provider. + /// + /// + /// An that provides references to intrinsic server objects + /// that are used to service HTTP requests. + /// + /// + /// is null or is an empty + /// string. + /// + public override string GetOutputCacheProviderName(HttpContext context) + { + if (GetOutputCacheProviderNameCallback != null) + return GetOutputCacheProviderNameCallback(context); + + return base.GetOutputCacheProviderName(context); + } + + /// + /// Provides an application-wide implementation of the + /// property. + /// + /// + /// If the value of the parameter is "browser", the browser's + /// ; otherwise, null. + /// + /// + /// An object that contains information about the current Web + /// request. + /// + /// The custom string that specifies which cached response is used to respond to the current request. + public override string GetVaryByCustomString(HttpContext context, string custom) + { + if (GetVaryByCustomStringCallback != null) + return GetVaryByCustomStringCallback(context, custom); + + return base.GetVaryByCustomString(context, custom); + } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpContext.cs b/src/Web/FakeHttpContext.cs index 9d56fdf..4296d49 100644 --- a/src/Web/FakeHttpContext.cs +++ b/src/Web/FakeHttpContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Security.Principal; using System.Web; using System.Web.Caching; @@ -8,81 +7,146 @@ namespace FakeN.Web { - public class FakeHttpContext : HttpContextBase - { - private readonly HttpRequestBase request; - private readonly HttpResponseBase response; - private readonly HttpSessionStateBase session; - private readonly IDictionary items; - - private TraceContext trace; - private DateTime timestamp; - private bool skipAuthorization; - private HttpServerUtilityBase server; - private ProfileBase profile; - private IHttpHandler previousHandler; - private bool isPostNotification; - private bool isDebuggingEnabled; - private bool isCustomErrorEnabled; - private IHttpHandler handler; - private Exception error; - private RequestNotification currentNotification; - private IHttpHandler currentHandler; - private Cache cache; - private HttpApplication applicationInstance; - private HttpApplicationStateBase application; - private Exception[] allErrors; - - public FakeHttpContext( - HttpRequestBase request = null, - HttpResponseBase response = null, - HttpSessionStateBase session = null) - { - this.request = request ?? new FakeHttpRequest(); - this.response = response ?? new FakeHttpResponse(); - this.session = session ?? new FakeHttpSessionState(); - items = new Dictionary(); - User = new GenericPrincipal(new MutableIdentity(), new string[] { }); - } - - public override HttpRequestBase Request - { - get { return request; } - } - - public override HttpResponseBase Response - { - get { return response; } - } - - public override HttpSessionStateBase Session - { - get { return session; } - } - - public override IDictionary Items - { - get { return items; } - } - - public override IPrincipal User { get; set; } - - public override TraceContext Trace { get { return trace; } } - public override DateTime Timestamp { get { return timestamp; } } - public override bool SkipAuthorization { get { return skipAuthorization; } set { skipAuthorization = value; } } - public override HttpServerUtilityBase Server { get { return server; } } - public override ProfileBase Profile { get { return profile; } } - public override IHttpHandler PreviousHandler { get { return previousHandler; } } - public override bool IsPostNotification { get { return isPostNotification; } } - public override bool IsDebuggingEnabled { get { return isDebuggingEnabled; } } - public override bool IsCustomErrorEnabled { get { return isCustomErrorEnabled; } } - public override IHttpHandler Handler { get { return handler; } set { handler = value; } } - public override Exception Error { get { return error; } } - public override RequestNotification CurrentNotification { get { return currentNotification; } } - public override IHttpHandler CurrentHandler { get { return currentHandler; } } - public override Cache Cache { get { return cache; } } - public override HttpApplication ApplicationInstance { get { return applicationInstance; } set { applicationInstance = value; } } - public override HttpApplicationStateBase Application { get { return application; } } - public override Exception[] AllErrors { get { return allErrors; } } - } + public class FakeHttpContext : HttpContextBase + { + private readonly ProfileBase _profile; + private readonly HttpRequestBase request; + private readonly HttpResponseBase response; + private readonly HttpSessionStateBase session; + + public FakeHttpContext( + HttpRequestBase request = null, + HttpResponseBase response = null, + HttpSessionStateBase session = null) + { + this.request = request; + this.response = response; + this.session = session; + _profile = new ProfileBase(); + Control = new FakeHttpContextControl(this); + } + + public FakeHttpContext(FakeHttpContextControl control) + { + Control = control; + _profile = new ProfileBase(); + } + + public FakeHttpContext() + { + Control = new FakeHttpContextControl(); + _profile = new ProfileBase(); + } + + /// + /// Used to control this context. + /// + public FakeHttpContextControl Control { get; private set; } + + public override HttpRequestBase Request + { + get { return request ?? Control.Request; } + } + + public override HttpResponseBase Response + { + get { return response ?? Control.Response; } + } + + public override HttpSessionStateBase Session + { + get { return session ?? Control.Session; } + } + + public override IDictionary Items + { + get { return Control.Items; } + } + + public override IPrincipal User { get { return Control.User; } set { Control.User = value; } } + + public override TraceContext Trace + { + get { return Control.TraceContext; } + } + + public override DateTime Timestamp + { + get { return Control.TimeStamp; } + } + + public override bool SkipAuthorization { get; set; } + + public override HttpServerUtilityBase Server + { + get { return Control.Server; } + } + + public override ProfileBase Profile + { + get { return _profile; } + } + + public override IHttpHandler PreviousHandler + { + get { return Control.PreviousHandler; } + } + + public override bool IsPostNotification + { + get { return Control.IsPostNotification; } + } + + public override bool IsDebuggingEnabled + { + get { return Control.IsDebuggingEnabled; } + } + + public override bool IsCustomErrorEnabled + { + get { return Control.IsCustomErrorsEnabled; } + } + + public override IHttpHandler Handler + { + get { return Control.Handler; } + set { Control.Handler = value; } + } + + public override Exception Error + { + get { return Control.Error; } + } + + public override RequestNotification CurrentNotification + { + get { return Control.CurrentNotification; } + } + + public override IHttpHandler CurrentHandler + { + get { return Control.CurrentHandler; } + } + + public override Cache Cache + { + get { return Control.Cache; } + } + + public override HttpApplication ApplicationInstance + { + get { return Control.ApplicationInstance; } + set { Control.ApplicationInstance = new FakeHttpApplication(value); } + } + + public override HttpApplicationStateBase Application + { + get { return Control.Application; } + } + + public override Exception[] AllErrors + { + get { return Control.AllErrors.ToArray(); } + } + } } \ No newline at end of file diff --git a/src/Web/FakeHttpContextBuilder.cs b/src/Web/FakeHttpContextBuilder.cs new file mode 100644 index 0000000..20cac89 --- /dev/null +++ b/src/Web/FakeHttpContextBuilder.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Security.Principal; + +namespace FakeN.Web +{ + /// + /// Use this class to build a fake request easily. + /// + /// + /// Basic + /// + /// Hello World!"); + /// + /// var httpContext = builder.Build(); + /// ]]> + /// + /// With session: + /// + /// Hello World!") + /// .UsingSession(new { UserId = 1 }); + /// + /// var httpContext = builder.Build(); + /// ]]> + /// + /// With principal: + /// + /// Hello World!") + /// .UsePrincipal(new GenericPrincipal(/*....*)); + /// + /// var httpContext = builder.Build(); + /// ]]> + /// + /// + public class FakeHttpContextBuilder + { + private FakeHttpRequestBuilder _request; + private FakeHttpResponseBuilder _response; + private IDictionary _session; + private IPrincipal _principal; + + /// + /// Used to serialize the body for . + /// + [ThreadStatic] + public static Func BodySerializer; + public FakeHttpContext Build() + { + var request = _request == null ? new FakeHttpRequest(new Uri("http://localhost/")) : _request.BuildRequest(); + var response = _response == null ? new FakeHttpResponse() : _response.BuildResponse(); + + request.InputStream.Position = 0; + response.OutputStream.Position = 0; + + var ctx = new FakeHttpContext(request, response, _session == null ? null : new FakeHttpSessionState(_session)); + + if (_principal != null) + ctx.User = _principal; + return ctx; + } + + public FakeHttpRequestBuilder Post(string url) + { + _request = new FakeHttpRequestBuilder(this, url, "POST"); + return _request; + } + + public FakeHttpRequestBuilder Put(string url) + { + _request = new FakeHttpRequestBuilder(this, url, "PUT"); + return _request; + } + + public FakeHttpRequestBuilder Get(string url) + { + _request = new FakeHttpRequestBuilder(this, url, "GET"); + return _request; + } + + public FakeHttpRequestBuilder Delete(string url) + { + _request = new FakeHttpRequestBuilder(this, url, "DELETE"); + return _request; + } + + public FakeHttpContextBuilder LocalFolderIs(string folder) + { + return this; + } + + public FakeHttpResponseBuilder RespondWith() + { + _response = new FakeHttpResponseBuilder(this); + return _response; + } + + public FakeHttpContextBuilder RespondWith(string htmlBody, string contentType = "text/html") + { + _response = new FakeHttpResponseBuilder(this, htmlBody, contentType); + return this; + } + + /// + /// Requires that is specified. + /// + /// + /// + /// + public FakeHttpContextBuilder RespondWith(object body, string contentType = "application/json") + { + _response = new FakeHttpResponseBuilder(this, BodySerializer(body), contentType); + return this; + } + + + + public FakeHttpContextBuilder UsingSession(IDictionary session) + { + _session = session; + return this; + } + + public FakeHttpContextBuilder UsePrincipal(IPrincipal principal) + { + _principal = principal; + return this; + } + + public FakeHttpContextBuilder UsingSession(object anonObject) + { + _session = Helper.ToDictionary(anonObject); + return this; + } + + public FakeHttpContextBuilder VirtualRootIs(string absolutePath) + { + return this; + } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpContextControl.cs b/src/Web/FakeHttpContextControl.cs new file mode 100644 index 0000000..cf10974 --- /dev/null +++ b/src/Web/FakeHttpContextControl.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Security.Principal; +using System.Web; +using System.Web.Caching; + +namespace FakeN.Web +{ + public class FakeHttpContextControl + { + private readonly FakeHttpContext _httpContext; + private FakeHttpRequest _request; + private FakeHttpResponse _response; + + public FakeHttpContextControl(FakeHttpRequest request = null, + FakeHttpResponse response = null, + FakeHttpSessionState session = null) + { + AllErrors = new List(); + ApplicationInstance = new FakeHttpApplication(); + Application = new FakeHttpStateBase(); + CurrentHandler = new DefaultHttpHandler(); + Items = new Dictionary(); + Request = request ?? new FakeHttpRequest(); + Response = response ?? new FakeHttpResponse(); + Server = new FakeHttpServerUtilityBase(); + Session = session ?? new FakeHttpSessionState(); + TimeStamp = DateTime.Now; + User = new GenericPrincipal(new MutableIdentity(), new string[] { }); + } + + public FakeHttpContextControl() + : this(null, null, null) + { + } + + public FakeHttpContextControl(FakeHttpContext httpContext) + : this() + { + _httpContext = httpContext; + } + + public IPrincipal User { get; set; } + public Dictionary Items { get; set; } + + public FakeHttpRequest Request + { + get { return _request; } + set + { + _request = value; + _request.Control.HttpContext = _httpContext; + } + } + + public FakeHttpResponse Response + { + get { return _response; } + set + { + _response = value; + } + } + + public HttpSessionStateBase Session { get; set; } + public TraceContext TraceContext { get; set; } + public FakeHttpServerUtilityBase Server { get; set; } + public IHttpHandler PreviousHandler { get; set; } + public bool IsPostNotification { get; set; } + public bool IsDebuggingEnabled { get; set; } + public Exception Error { get; set; } + public RequestNotification CurrentNotification { get; set; } + public IHttpHandler CurrentHandler { get; set; } + public Cache Cache { get; set; } + public FakeHttpApplication ApplicationInstance { get; set; } + public FakeHttpStateBase Application { get; set; } + public List AllErrors { get; set; } + public DateTime TimeStamp { get; set; } + public bool IsCustomErrorsEnabled { get; set; } + public IHttpHandler Handler { get; set; } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpRequest.cs b/src/Web/FakeHttpRequest.cs index 13d9d48..b75b368 100644 --- a/src/Web/FakeHttpRequest.cs +++ b/src/Web/FakeHttpRequest.cs @@ -9,112 +9,68 @@ using System.Web.Routing; namespace FakeN.Web { - - public interface IHttpObject { } - - public class FakeHttpRequest : HttpRequestBase, IHttpObject { - private readonly NameValueCollection form; - private readonly NameValueCollection queryString; - private readonly NameValueCollection headers; - private readonly NameValueCollection serverVariables; - private readonly HttpCookieCollection cookies; - private HttpFileCollectionBase files; - private Uri url; - private string method; - private bool isLocal; - private string userHostAddress; - private string applicationPath; - private string[] acceptTypes; - private Stream inputStream; - private bool isAuthenticated; - - private string anonymousId; - private string appRelativeCurrentExecutionFilePath; - private HttpBrowserCapabilitiesBase browser; - private ChannelBinding httpChannelBinding; - private HttpClientCertificate clientCertificate; - private int contentLength; - private string currentExecutionFilePath; - private Stream filter; - private bool isSecureConnection; - private WindowsIdentity logonUserIdentity; - private NameValueCollection @params; - private string physicalApplicationPath; - private string physicalPath; - private RequestContext requestContext; - private int totalBytes; - private Uri urlReferrer; - private string userAgent; - private string[] userLanguages; - private string userHostName; - private string pathInfo; - - private static NameValueCollection ParseQueryString(string url) { - return HttpUtility.ParseQueryString(url); - } - + public class FakeHttpRequest : HttpRequestBase, IHttpObject { + public FakeHttpRequest(Uri url = null, string method = "GET") { - this.url = url ?? new Uri("http://localhost"); - this.method = method; - acceptTypes = new string[] { }; - queryString = ParseQueryString(this.url.Query); - form = new NameValueCollection(); - headers = new NameValueCollection(); - serverVariables = new NameValueCollection(); - cookies = new HttpCookieCollection(); + Control = new FakeHttpRequestControl(url, method); } - public FakeHttpRequest SetUrl(Uri url) { - this.url = url; - queryString.Clear(); - queryString.Add(ParseQueryString(url.Query)); + public FakeHttpRequest SetUrl(Uri url) + { + Control.SetUrl(url); return this; } + /// + /// Used to control what this request returns. + /// + public FakeHttpRequestControl Control { get; set; } + public override string this[string key] { - get { return new NameValueCollection { form, queryString }[key]; } + get { return new NameValueCollection { Control.Form, Control.QueryString }[key]; } } - public override bool IsAuthenticated { get { return isAuthenticated; } } - public override Uri Url { get { return url; } } - public override bool IsLocal { get { return isLocal; } } - public override string ApplicationPath { get { return applicationPath; } } - public override string HttpMethod { get { return method; } } - public override string UserHostAddress { get { return userHostAddress; } } - public override string[] AcceptTypes { get { return acceptTypes; } } + public override bool IsAuthenticated { get { return Control.IsAuthenticated; } } + public override Uri Url { get { return Control.Url; } } + public override bool IsLocal { get { return Control.IsLocal; } } + public override string ApplicationPath { get { return Control.ApplicationPath; } } + public override string HttpMethod { get { return Control.HttpMethod; } } + public override string UserHostAddress { get { return Control.UserHostAddress; } } + public override string[] AcceptTypes { get { return Control.AcceptTypes.ToArray(); } } public override string RequestType { get; set; } public override string ContentType { get; set; } public override Encoding ContentEncoding { get; set; } public override void ValidateInput() { } - public override string RawUrl { get { return url.PathAndQuery; } } - public override NameValueCollection Form { get { return form; } } - public override NameValueCollection QueryString { get { return queryString; } } - public override NameValueCollection Headers { get { return headers; } } - public override NameValueCollection ServerVariables { get { return serverVariables; } } - public override HttpCookieCollection Cookies { get { return cookies; } } - public override HttpFileCollectionBase Files { get { return files; } } + public override string RawUrl { get { return Control.Url.PathAndQuery; } } + public override NameValueCollection Form { get { return Control.Form; } } + public override NameValueCollection QueryString { get { return Control.QueryString; } } + public override NameValueCollection Headers { get { return Control.Headers; } } + public override NameValueCollection ServerVariables { get { return Control.ServerVariables; } } + public override HttpCookieCollection Cookies { get { return Control.Cookies; } } + public override HttpFileCollectionBase Files { get { return Control.Files; } } public override string Path { get { return Url.AbsolutePath; } } public override string FilePath { get { return Url.AbsolutePath; } } - public override string PathInfo { get { return pathInfo; } } - public override Stream InputStream { get { return inputStream; } } - public override string AnonymousID { get { return anonymousId; } } - public override string AppRelativeCurrentExecutionFilePath { get { return appRelativeCurrentExecutionFilePath; } } - public override HttpBrowserCapabilitiesBase Browser { get { return browser; } } - public override ChannelBinding HttpChannelBinding { get { return httpChannelBinding; } } - public override HttpClientCertificate ClientCertificate { get { return clientCertificate; } } - public override int ContentLength { get { return contentLength; } } - public override string CurrentExecutionFilePath { get { return currentExecutionFilePath; } } - public override Stream Filter { get { return filter; } set { filter = value; } } - public override bool IsSecureConnection { get { return isSecureConnection; } } - public override WindowsIdentity LogonUserIdentity { get { return logonUserIdentity; } } - public override NameValueCollection Params { get { return @params; } } - public override string PhysicalApplicationPath { get { return physicalApplicationPath; } } - public override string PhysicalPath { get { return physicalPath; } } - public override RequestContext RequestContext { get { return requestContext; } } - public override int TotalBytes { get { return totalBytes; } } - public override Uri UrlReferrer { get { return urlReferrer; } } - public override string UserAgent { get { return userAgent; } } - public override string[] UserLanguages { get { return userLanguages; } } - public override string UserHostName { get { return userHostName; } } - } + public override string PathInfo { get { return Control.PathInfo; } } + public override Stream InputStream { get { return Control.InputStream; } } + public override string AnonymousID { get { return Control.AnonymousId; } } + public override string AppRelativeCurrentExecutionFilePath { get { return Control.AppRelativeCurrentExecutionFilePath; } } + public override HttpBrowserCapabilitiesBase Browser { get { return Control.Browser; } } + public override ChannelBinding HttpChannelBinding { get { return Control.HttpChannelBinding; } } + public override HttpClientCertificate ClientCertificate { get { return Control.ClientCertificate; } } + public override int ContentLength { get { return Control.ContentLength; } } + public override string CurrentExecutionFilePath { get { return Control.CurrentExecutionFilePath; } } + public override Stream Filter { get { return Control.Filter; } set { Control.Filter = value; } } + public override bool IsSecureConnection { get { return Control.IsSecureConnection; } } + public override WindowsIdentity LogonUserIdentity { get { return Control.LogonUserIdentity; } } + public override NameValueCollection Params { get { return Control.Params; } } + public override string PhysicalApplicationPath { get { return Control.PhysicalApplicationPath; } } + public override string PhysicalPath { get { return Control.PhysicalPath; } } + public override RequestContext RequestContext { get { return Control.RequestContext; } } + public override int TotalBytes { get { return Control.TotalBytes; } } + public override Uri UrlReferrer { get { return Control.UrlReferrer; } } + public override string UserAgent { get { return Control.UserAgent; } } + public override string[] UserLanguages { get { return Control.UserLanguages.ToArray(); } } + public override string UserHostName { get { return Control.UserHostName; } } + + } } \ No newline at end of file diff --git a/src/Web/FakeHttpRequestBuilder.cs b/src/Web/FakeHttpRequestBuilder.cs new file mode 100644 index 0000000..282dbf1 --- /dev/null +++ b/src/Web/FakeHttpRequestBuilder.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using System.Web; + +namespace FakeN.Web +{ + public class FakeHttpRequestBuilder + { + private readonly FakeHttpContextBuilder _builder; + private readonly string _url; + private FakeHttpRequest _request; + public FakeHttpRequestBuilder(FakeHttpContextBuilder builder, string url, string method) + { + _builder = builder; + _url = url; + _request = new FakeHttpRequest(new Uri(url)) {Control = {HttpMethod = method}}; + } + + public FakeHttpRequestBuilder WithForm(NameValueCollection form) + { + _request.Control.Form = form; + return this; + } + + public FakeHttpRequestBuilder WithForm(object anonObject) + { + _request.Control.Form = Helper.ToNameValue(anonObject); + return this; + } + + public FakeHttpRequestBuilder WithBody(string body, string contentType = "application/x-form-url-encoded") + { + _request.Control.Headers.Add("Content-Type", contentType); + var buf = Encoding.UTF8.GetBytes(body); + _request.Control.InputStream.Write(buf,0,buf.Length); + return this; + } + + public FakeHttpRequestBuilder WithJsonBody(string body) + { + _request.Control.Headers.Add("Content-Type", "application/json"); + var buf = Encoding.UTF8.GetBytes(body); + _request.Control.InputStream.Write(buf,0,buf.Length); + return this; + } + + public FakeHttpRequestBuilder WithBinaryBody(Stream body, string contentType = "application/octet-stream") + { + _request.Control.Headers.Add("Content-Type", contentType); + _request.Control.InputStream = body; + return this; + } + + public FakeHttpRequestBuilder WithBinaryBody(byte[] body, string contentType = "application/octet-stream") + { + _request.Control.Headers.Add("Content-Type", contentType); + _request.Control.InputStream.Write(body, 0, body.Length); + return this; + } + + public FakeHttpRequest BuildRequest() + { + return _request; + } + + + public FakeHttpResponseBuilder RespondWith() + { + return _builder.RespondWith(); + } + + public FakeHttpContextBuilder RespondWith(string body, string contentType = "text/html") + { + return _builder.RespondWith(body, contentType); + } + + /// + /// Requires that is specified. + /// + /// + /// + /// + public FakeHttpContextBuilder RespondWith(object body, string contentType = "application/json") + { + return _builder.RespondWith(body, contentType); + } + + public FakeHttpRequestBuilder AddCookie(string name, string value) + { + _request.Cookies.Add(new HttpCookie(name, value)); + return this; + } + + public FakeHttpRequestBuilder AddCookie(HttpCookie cookie) + { + _request.Cookies.Add(cookie); + return this; + } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpRequestControl.cs b/src/Web/FakeHttpRequestControl.cs new file mode 100644 index 0000000..51d24a9 --- /dev/null +++ b/src/Web/FakeHttpRequestControl.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Security.Authentication.ExtendedProtection; +using System.Security.Principal; +using System.Web; +using System.Web.Routing; + +namespace FakeN.Web +{ + public class FakeHttpRequestControl + { + private NameValueCollection _params; + private int _contentLength = -1; + private FakeHttpContext _httpContext; + + public FakeHttpRequestControl(Uri url = null, string httpMethod = "GET") + { + Url = url ?? new Uri("http://localhost"); + HttpMethod = httpMethod; + AcceptTypes = new List(); + UserLanguages = new List() {"en-US"}; + QueryString = HttpUtility.ParseQueryString(Url.Query); + Form = new NameValueCollection(); + Headers = new NameValueCollection(); + ServerVariables = new NameValueCollection(); + Cookies = new HttpCookieCollection(); + InputStream = new MemoryStream(); + PathInfo = ""; + PhysicalApplicationPath = "C:\\temp"; + PhysicalPath = Path.Combine(PhysicalApplicationPath, Url.AbsolutePath.TrimStart('/').Replace('/', '\\')); + RequestContext = new FakeRequestContext(); + } + + public FakeHttpContext HttpContext + { + get { return _httpContext; } + set + { + _httpContext = value; + RequestContext.HttpContext = value; + } + } + + public HttpCookieCollection Cookies { get; set; } + public NameValueCollection Form { get; set; } + public NameValueCollection Headers { get; set; } + public NameValueCollection QueryString { get; set; } + public NameValueCollection ServerVariables { get; set; } + public List AcceptTypes { get; set; } + public string AnonymousId { get; set; } + public string ApplicationPath { get; set; } + public string AppRelativeCurrentExecutionFilePath { get; set; } + public HttpBrowserCapabilitiesBase Browser { get; set; } + public HttpClientCertificate ClientCertificate { get; set; } + + public int ContentLength + { + get + { + return (int) (_contentLength != -1 ? _contentLength : InputStream.Length); + } + set { _contentLength = value; } + } + + public string CurrentExecutionFilePath { get; set; } + public HttpFileCollectionBase Files { get; set; } + public Stream Filter { get; set; } + public ChannelBinding HttpChannelBinding { get; set; } + public Stream InputStream { get; set; } + public bool IsAuthenticated { get; set; } + public bool IsLocal { get; set; } + public bool IsSecureConnection { get; set; } + public WindowsIdentity LogonUserIdentity { get; set; } + public string HttpMethod { get; set; } + + public NameValueCollection Params + { + get + { + return _params ?? QueryString; + } + set { _params = value; } + } + + public string PathInfo { get; set; } + public string PhysicalApplicationPath { get; set; } + public string PhysicalPath { get; set; } + public RequestContext RequestContext { get; set; } + public int TotalBytes { get; set; } + public Uri Url { get; set; } + public Uri UrlReferrer { get; set; } + public string UserAgent { get; set; } + public string UserHostAddress { get; set; } + public string UserHostName { get; set; } + public List UserLanguages { get; set; } + + public FakeHttpRequestControl SetUrl(Uri url) + { + this.Url = url; + QueryString.Clear(); + QueryString.Add(HttpUtility.ParseQueryString(url.Query)); + return this; + } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpResponse.cs b/src/Web/FakeHttpResponse.cs index f3fe398..3a12f6a 100644 --- a/src/Web/FakeHttpResponse.cs +++ b/src/Web/FakeHttpResponse.cs @@ -6,94 +6,164 @@ namespace FakeN.Web { - public class FakeHttpResponse : HttpResponseBase - { - private Func appPathModifier; - - private bool buffer; - - private bool bufferOutput; - - private string cacheControl; - - private string charset; - - private Encoding contentEncoding; - - private string contentType; - - private HttpCookieCollection cookies; - - private int expires; - - private DateTime expiresAbsolute; - - private Stream filter; - - private NameValueCollection headers; - - private Encoding headerEncoding; - - private bool isClientConnected; - - private bool isRequestBeingRedirected; - - private TextWriter output; - - private Stream outputStream; - - private string redirectLocation; - - private string status; - - private bool suppressContent; - - private bool trySkipIisCustomErrors; - - public FakeHttpResponse() - { - appPathModifier = x => x; - } - - public override HttpCachePolicyBase Cache - { - get { return new FakeHttpCachePolicy(); } - } - - public override string ApplyAppPathModifier(string virtualPath) - { - return appPathModifier(virtualPath); - } - - public FakeHttpResponse SetAppPathModifier(Func appPathModifier) - { - this.appPathModifier = appPathModifier; - return this; - } - - public override int StatusCode { get; set; } - public override string StatusDescription { get; set; } - public override int SubStatusCode { get; set; } - - public override bool Buffer { get { return buffer; } set { buffer = value; } } - public override bool BufferOutput { get { return bufferOutput; } set { bufferOutput = value; } } - public override string CacheControl { get { return cacheControl; } set { cacheControl = value; } } - public override string Charset { get { return charset; } set { charset = value; } } - public override Encoding ContentEncoding { get { return contentEncoding; } set { contentEncoding = value; } } - public override string ContentType { get { return contentType; } set { contentType = value; } } - public override HttpCookieCollection Cookies { get { return cookies; } } - public override int Expires { get { return expires; } set { expires = value; } } - public override DateTime ExpiresAbsolute { get { return expiresAbsolute; } set { expiresAbsolute = value; } } - public override Stream Filter { get { return filter; } set { filter = value; } } - public override NameValueCollection Headers { get { return headers; } } - public override Encoding HeaderEncoding { get { return headerEncoding; } set { headerEncoding = value; } } - public override bool IsClientConnected { get { return isClientConnected; } } - public override bool IsRequestBeingRedirected { get { return isRequestBeingRedirected; } } - public override TextWriter Output { get { return output; } set { output = value; } } - public override Stream OutputStream { get { return outputStream; } } - public override string RedirectLocation { get { return redirectLocation; } set { redirectLocation = value; } } - public override string Status { get { return status; } set { status = value; } } - public override bool SuppressContent { get { return suppressContent; } set { suppressContent = value; } } - public override bool TrySkipIisCustomErrors { get { return trySkipIisCustomErrors; } set { trySkipIisCustomErrors = value; } } - } + public class FakeHttpResponse : HttpResponseBase + { + public FakeHttpResponse() + { + Control = new FakeHttpResponseControl(); + } + + public FakeHttpResponseControl Control { get; set; } + + public override HttpCachePolicyBase Cache + { + get { return new FakeHttpCachePolicy(); } + } + + public override int StatusCode + { + get { return Control.StatusCode; } + set { Control.StatusCode = value; } + } + + public override string StatusDescription + { + get { return Control.StatusDescription; } + set { Control.StatusDescription = value; } + } + + public override int SubStatusCode { get; set; } + + public override bool Buffer + { + get { return Control.Buffer; } + set { Control.Buffer = value; } + } + + public override bool BufferOutput + { + get { return Control.BufferOutput; } + set { Control.BufferOutput = value; } + } + + public override string CacheControl + { + get { return Control.CacheControl; } + set { Control.CacheControl = value; } + } + + public override string Charset + { + get { return Control.Charset; } + set { Control.Charset = value; } + } + + public override Encoding ContentEncoding + { + get { return Control.ContentEncoding; } + set { Control.ContentEncoding = value; } + } + + public override string ContentType + { + get { return Control.ContentType; } + set { Control.ContentType = value; } + } + + public override HttpCookieCollection Cookies + { + get { return Control.Cookies; } + } + + public override int Expires + { + get { return Control.Expires; } + set { Control.Expires = value; } + } + + public override DateTime ExpiresAbsolute + { + get { return Control.ExpiresAbsolute; } + set { Control.ExpiresAbsolute = value; } + } + + public override Stream Filter + { + get { return Control.Filter; } + set { Control.Filter = value; } + } + + public override NameValueCollection Headers + { + get { return Control.Headers; } + } + + public override Encoding HeaderEncoding + { + get { return Control.HeaderEncoding; } + set { Control.HeaderEncoding = value; } + } + + public override bool IsClientConnected + { + get { return Control.IsClientConnected; } + } + + public override bool IsRequestBeingRedirected + { + get { return Control.IsRequestBeingRedirected; } + } + + public override TextWriter Output + { + get { return Control.Output; } + set { Control.Output = value; } + } + + public override Stream OutputStream + { + get { return Control.OutputStream; } + } + + public override string RedirectLocation + { + get { return Control.RedirectLocation; } + set { Control.RedirectLocation = value; } + } + + /// + /// When overridden in a derived class, gets or sets the Status value that is returned to the client. + /// + /// + /// The status of the HTTP output. For information about valid status codes, see HTTP Status Codes on the MSDN Web site. + /// + public override string Status + { + get { return Control.Status ?? StatusCode.ToString(); } + set { Control.Status = value; } + } + + public override bool SuppressContent + { + get { return Control.SuppressContent; } + set { Control.SuppressContent = value; } + } + + public override bool TrySkipIisCustomErrors + { + get { return Control.TrySkipIisCustomErrors; } + set { Control.TrySkipIisCustomErrors = value; } + } + + public override string ApplyAppPathModifier(string virtualPath) + { + return Control.AppPathModifier(virtualPath); + } + + public FakeHttpResponse SetAppPathModifier(Func appPathModifier) + { + Control.AppPathModifier = appPathModifier; + return this; + } + } } \ No newline at end of file diff --git a/src/Web/FakeHttpResponseBuilder.cs b/src/Web/FakeHttpResponseBuilder.cs new file mode 100644 index 0000000..74c1aeb --- /dev/null +++ b/src/Web/FakeHttpResponseBuilder.cs @@ -0,0 +1,78 @@ +using System.IO; +using System.Net; +using System.Text; + +namespace FakeN.Web +{ + public class FakeHttpResponseBuilder + { + private readonly FakeHttpContextBuilder _httpContextBuilder; + private readonly FakeHttpResponse _response = new FakeHttpResponse(); + + public FakeHttpResponseBuilder(FakeHttpContextBuilder httpContextBuilder) + { + _httpContextBuilder = httpContextBuilder; + } + + public FakeHttpResponseBuilder(FakeHttpContextBuilder httpContextBuilder, string htmlBody) + { + _httpContextBuilder = httpContextBuilder; + WithBody(htmlBody); + } + + public FakeHttpResponseBuilder(FakeHttpContextBuilder httpContextBuilder, string body, string contentType) + { + _httpContextBuilder = httpContextBuilder; + WithBody(body, contentType); + } + + public FakeHttpResponseBuilder Status(HttpStatusCode statusCode, string statusDescription = null) + { + _response.StatusCode = (int)statusCode; + _response.StatusDescription = statusDescription ?? statusCode.ToString(); + return this; + } + + public FakeHttpResponseBuilder Status(int statusCode, string statusDescription = null) + { + _response.StatusCode = statusCode; + _response.StatusDescription = statusDescription ?? statusCode.ToString(); + return this; + } + + + public FakeHttpResponseBuilder WithBody(string body, string contentType = "text/html") + { + _response.Control.Headers.Add("Content-Type", contentType); + var buf = Encoding.UTF8.GetBytes(body); + _response.Control.OutputStream.Write(buf, 0, buf.Length); + return this; + } + + public FakeHttpResponseBuilder WithJsonBody(string body) + { + _response.Control.Headers.Add("Content-Type", "application/json"); + var buf = Encoding.UTF8.GetBytes(body); + _response.Control.OutputStream.Write(buf, 0, buf.Length); + return this; + } + + public FakeHttpResponseBuilder WithBinaryBody(Stream body, string contentType = "application/octet-stream") + { + _response.Control.Headers.Add("Content-Type", contentType); + _response.Control.OutputStream = body; + return this; + } + + public FakeHttpResponse BuildResponse() + { + return _response; + } + + public FakeHttpContext Build() + { + return _httpContextBuilder.Build(); + } + + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpResponseControl.cs b/src/Web/FakeHttpResponseControl.cs new file mode 100644 index 0000000..a9a11c0 --- /dev/null +++ b/src/Web/FakeHttpResponseControl.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using System.Web; + +namespace FakeN.Web +{ + public class FakeHttpResponseControl + { + public FakeHttpResponseControl() + { + AppPathModifier = x => x; + Charset = "utf-8"; + ContentEncoding = Encoding.UTF8; + ContentType = "text/html"; + Cookies = new HttpCookieCollection(); + Expires = 60; + ExpiresAbsolute = DateTime.Now.AddSeconds(60); + Headers = new NameValueCollection(); + OutputStream = new MemoryStream(); + Output = new StreamWriter(OutputStream); + StatusCode = 200; + StatusDescription = "OK"; + } + + public Func AppPathModifier { get; set; } + + public bool Buffer { get; set; } + + public bool BufferOutput { get; set; } + + public string CacheControl { get; set; } + + public string Charset { get; set; } + + public Encoding ContentEncoding { get; set; } + + public string ContentType { get; set; } + + public HttpCookieCollection Cookies { get; set; } + + public int Expires { get; set; } + + public DateTime ExpiresAbsolute { get; set; } + + public Stream Filter { get; set; } + + public NameValueCollection Headers { get; set; } + + public Encoding HeaderEncoding { get; set; } + + public bool IsClientConnected { get; set; } + + public bool IsRequestBeingRedirected { get; set; } + + public TextWriter Output { get; set; } + + public Stream OutputStream { get; set; } + + public string RedirectLocation { get; set; } + + public string Status { get; set; } + + public bool SuppressContent { get; set; } + + public bool TrySkipIisCustomErrors { get; set; } + public int StatusCode { get; set; } + public string StatusDescription { get; set; } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpServerUtilityBase.cs b/src/Web/FakeHttpServerUtilityBase.cs new file mode 100644 index 0000000..a875ef6 --- /dev/null +++ b/src/Web/FakeHttpServerUtilityBase.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Web; + +namespace FakeN.Web +{ + public class FakeHttpServerUtilityBase : HttpServerUtilityBase + { + public FakeHttpServerUtilityBase() + { + TransferRequests = new List(); + MappedPaths = new Dictionary(); + } + + /// + /// Contains all invocations to one of the TransferRequest overloads. + /// + public List TransferRequests { get; private set; } + + /// + /// Use this property to be able to return custom values for MapPath. + /// + public IDictionary MappedPaths { get; set; } + + /// + /// When overridden in a derived class, returns the physical file path that corresponds to the specified virtual path + /// on the Web server. + /// + /// + /// The physical file path that corresponds to . + /// + /// The virtual path to get the physical path for. + /// Always. + public override string MapPath(string path) + { + string mappedPath; + return !MappedPaths.TryGetValue(path, out mappedPath) + ? "/teststub/" + : mappedPath; + } + + /// + /// When overridden in a derived class, terminates execution of the current process and starts execution of a new + /// request, using a custom HTTP handler and a value that specifies whether to clear the + /// and + /// collections. + /// + /// The HTTP handler to transfer the current request to. + /// + /// true to preserve the and + /// collections; false to clear the + /// and collections. + /// + /// Always. + public override void Transfer(IHttpHandler handler, bool preserveForm) + { + TransferRequests.Add(new TransferRequest(handler, preserveForm)); + //base.Transfer(handler, preserveForm); + } + + /// + /// When overridden in a derived class, asynchronously executes the end point at the specified URL. + /// + /// The URL of the page or handler to execute. + /// Always. + public override void TransferRequest(string path) + { + TransferRequests.Add(new TransferRequest(path)); + } + + /// + /// When overridden in a derived class, asynchronously executes the endpoint at the specified URL and specifies whether + /// to clear the and + /// collections. + /// + /// The URL of the page or handler to execute. + /// + /// true to preserve the and + /// collections; false to clear the + /// and collections. + /// + /// Always. + public override void TransferRequest(string path, bool preserveForm) + { + TransferRequests.Add(new TransferRequest(path, preserveForm)); + } + + /// + /// When overridden in a derived class, asynchronously executes the endpoint at the specified URL by using the + /// specified HTTP method and headers. + /// + /// The URL of the page or handler to execute. + /// + /// true to preserve the and + /// collections; false to clear the + /// and collections. + /// + /// + /// The HTTP method (GET, POST, and so on) to use for the new request. If null, the HTTP method of the + /// original request is used. + /// + /// A collection of request headers for the new request. + /// Always. + public override void TransferRequest(string path, bool preserveForm, string method, NameValueCollection headers) + { + TransferRequests.Add(new TransferRequest(path, preserveForm, method, headers)); + } + } +} \ No newline at end of file diff --git a/src/Web/FakeHttpSessionState.cs b/src/Web/FakeHttpSessionState.cs index 9826926..76186f0 100644 --- a/src/Web/FakeHttpSessionState.cs +++ b/src/Web/FakeHttpSessionState.cs @@ -1,4 +1,6 @@ -using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Web; using System.Web.SessionState; @@ -27,7 +29,17 @@ public FakeHttpSessionState() data = new SessionStateCollection(); } - public override object this[string name] + public FakeHttpSessionState(IDictionary session) + { + if (session == null) throw new ArgumentNullException("session"); + data = new SessionStateCollection(); + foreach (var kvp in session) + { + data[kvp.Key] = kvp.Value; + } + } + + public override object this[string name] { get { return data[name]; } set { data[name] = value; } diff --git a/src/Web/FakeHttpStateBase.cs b/src/Web/FakeHttpStateBase.cs new file mode 100644 index 0000000..f94d33e --- /dev/null +++ b/src/Web/FakeHttpStateBase.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.Serialization; +using System.Web; + +namespace FakeN.Web +{ + /// + /// Use AllStates to control the content + /// + public class FakeHttpStateBase : HttpApplicationStateBase + { + public FakeHttpStateBase() + { + AllStates = new Dictionary(); + } + + public Dictionary AllStates { get; set; } + + /// + /// When overridden in a derived class, gets the access keys for the objects in the collection. + /// + /// + /// An array of state object keys. + /// + /// Always. + public override string[] AllKeys + { + get { return AllStates.Keys.ToArray(); } + } + + /// + /// Gets a instance that + /// contains all the keys in the instance. + /// + /// + /// A instance that contains + /// all the keys in the instance. + /// + public override KeysCollection Keys + { + get + { + var col = new NameValueCollection(); + foreach (var kvp in AllStates) + { + col.Add(kvp.Key, kvp.Value.ToString()); + } + return col.Keys; + } + } + + /// + /// When overridden in a derived class, gets a reference to the + /// object. + /// + /// + /// A reference to the object. + /// + /// Always. + public override HttpApplicationStateBase Contents + { + get { return this; } + } + + /// + /// When overridden in a derived class, gets the number of objects in the collection. + /// + /// + /// The number of objects in the collection. + /// + /// Always. + public override int Count + { + get { return AllStates.Count; } + } + + /// + /// When overridden in a derived class, gets a state object by index. + /// + /// + /// The object referenced by . + /// + /// The index of the object in the collection. + /// Always. + public override object this[int index] + { + get { return Get(index); } + } + + /// + /// When overridden in a derived class, gets a state object by name. + /// + /// + /// The object referenced by . + /// + /// The name of the object in the collection. + /// Always. + public override object this[string name] + { + get { return AllStates[name]; } + set { AllStates[name] = value; } + } + + /// + /// When overridden in a derived class, adds a new object to the collection. + /// + /// The name of the object to add to the collection. + /// The value of the object. + /// Always. + public override void Add(string name, object value) + { + AllStates.Add(name, value); + } + + /// + /// When overridden in a derived class, removes all objects from the collection. + /// + /// Always. + public override void Clear() + { + AllStates.Clear(); + } + + /// + /// When overridden in a derived class, copies the elements of the collection to an array, starting at the specified + /// index in the array. + /// + /// + /// The one-dimensional array that is the destination for the elements that are copied from the + /// collection. The array must have zero-based indexing. + /// + /// The zero-based index in at which to begin copying. + /// Always. + public override void CopyTo(Array array, int index) + { + var items = AllStates.Values.ToArray(); + Array.Copy(items, 0, array, index, items.Length); + } + + /// + /// When overridden in a derived class, gets a state object by index. + /// + /// + /// The object referenced by . + /// + /// The index of the application state object to get. + /// Always. + public override object Get(int index) + { + return AllStates.Values.ToArray()[index]; + } + + /// + /// When overridden in a derived class, gets a state object by name. + /// + /// + /// The object referenced by . + /// + /// The name of the object to get. + /// Always. + public override object Get(string name) + { + return AllStates[name]; + } + + /// + /// When overridden in a derived class, returns an enumerator that can be used to iterate through the collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// Always. + public override IEnumerator GetEnumerator() + { + return AllStates.GetEnumerator(); + } + + /// + /// When overridden in a derived class, gets the name of a state object by index. + /// + /// + /// The name of the application state object. + /// + /// The index of the application state object to get. + /// Always. + public override string GetKey(int index) + { + return AllStates.Keys.ToArray()[index]; + } + + /// + /// Implements the interface and returns the data needed to + /// serialize the instance. + /// + /// + /// A object that contains the + /// information required to serialize the + /// instance. + /// + /// + /// A object that contains the source + /// and destination of the serialized stream associated with the + /// instance. + /// + /// is null. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + AllStates.GetObjectData(info, context); + } + + /// + /// When overridden in a derived class, locks access to objects in the collection in order to enable synchronized + /// access. + /// + /// Always. + public override void Lock() + { + } + + /// + /// When overridden in a derived class, removes the named object from the collection. + /// + /// The name of the object to remove from the collection. + /// Always. + public override void Remove(string name) + { + AllStates.Remove(name); + } + + /// + /// When overridden in a derived class, removes all objects from the collection. + /// + /// Always. + public override void RemoveAll() + { + AllStates.Clear(); + } + + /// + /// When overridden in a derived class, removes a state object specified by index from the collection. + /// + /// The position in the collection of the item to remove. + /// Always. + public override void RemoveAt(int index) + { + var key = GetKey(index); + AllStates.Remove(key); + } + + /// + /// When overridden in a derived class, updates the value of an object in the collection. + /// + /// The name of the object to update. + /// The updated value of the object. + /// Always. + public override void Set(string name, object value) + { + AllStates[name] = value; + } + + /// + /// When overridden in a derived class, unlocks access to objects in the collection to enable synchronized access. + /// + /// Always. + public override void UnLock() + { + } + } +} \ No newline at end of file diff --git a/src/Web/FakeN.Web.csproj b/src/Web/FakeN.Web.csproj index 84b8630..27435eb 100644 --- a/src/Web/FakeN.Web.csproj +++ b/src/Web/FakeN.Web.csproj @@ -41,18 +41,31 @@ + + + + + + + + + + + + +