Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
namespace System.Web.Mvc.Async {
using System;
internal delegate ActionDescriptor ActionDescriptorCreator(string actionName, ControllerDescriptor controllerDescriptor);
}

View File

@@ -0,0 +1,28 @@
namespace System.Web.Mvc.Async {
using System;
using System.Collections.Generic;
public abstract class AsyncActionDescriptor : ActionDescriptor {
public abstract IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state);
public abstract object EndExecute(IAsyncResult asyncResult);
public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) {
// execute an asynchronous task synchronously
IAsyncResult asyncResult = BeginExecute(controllerContext, parameters, null, null);
AsyncUtil.WaitForAsyncResultCompletion(asyncResult, controllerContext.HttpContext.ApplicationInstance); // blocks
return EndExecute(asyncResult);
}
internal static AsyncManager GetAsyncManager(ControllerBase controller) {
IAsyncManagerContainer helperContainer = controller as IAsyncManagerContainer;
if (helperContainer == null) {
throw Error.AsyncCommon_ControllerMustImplementIAsyncManagerContainer(controller.GetType());
}
return helperContainer.AsyncManager;
}
}
}

View File

@@ -0,0 +1,194 @@
namespace System.Web.Mvc.Async {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Mvc.Resources;
internal sealed class AsyncActionMethodSelector {
public AsyncActionMethodSelector(Type controllerType) {
ControllerType = controllerType;
PopulateLookupTables();
}
public Type ControllerType {
get;
private set;
}
public MethodInfo[] AliasedMethods {
get;
private set;
}
public ILookup<string, MethodInfo> NonAliasedMethods {
get;
private set;
}
private AmbiguousMatchException CreateAmbiguousActionMatchException(IEnumerable<MethodInfo> ambiguousMethods, string actionName) {
string ambiguityList = CreateAmbiguousMatchList(ambiguousMethods);
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ActionMethodSelector_AmbiguousMatch,
actionName, ControllerType.Name, ambiguityList);
return new AmbiguousMatchException(message);
}
private AmbiguousMatchException CreateAmbiguousMethodMatchException(IEnumerable<MethodInfo> ambiguousMethods, string methodName) {
string ambiguityList = CreateAmbiguousMatchList(ambiguousMethods);
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.AsyncActionMethodSelector_AmbiguousMethodMatch,
methodName, ControllerType.Name, ambiguityList);
return new AmbiguousMatchException(message);
}
private static string CreateAmbiguousMatchList(IEnumerable<MethodInfo> ambiguousMethods) {
StringBuilder exceptionMessageBuilder = new StringBuilder();
foreach (MethodInfo methodInfo in ambiguousMethods) {
exceptionMessageBuilder.AppendLine();
exceptionMessageBuilder.AppendFormat(CultureInfo.CurrentCulture, MvcResources.ActionMethodSelector_AmbiguousMatchType, methodInfo, methodInfo.DeclaringType.FullName);
}
return exceptionMessageBuilder.ToString();
}
public ActionDescriptorCreator FindAction(ControllerContext controllerContext, string actionName) {
List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
switch (finalMethods.Count) {
case 0:
return null;
case 1:
MethodInfo entryMethod = finalMethods[0];
return GetActionDescriptorDelegate(entryMethod);
default:
throw CreateAmbiguousActionMatchException(finalMethods, actionName);
}
}
private ActionDescriptorCreator GetActionDescriptorDelegate(MethodInfo entryMethod) {
// Is this the FooAsync() / FooCompleted() pattern?
if (IsAsyncSuffixedMethod(entryMethod)) {
string completionMethodName = entryMethod.Name.Substring(0, entryMethod.Name.Length - "Async".Length) + "Completed";
MethodInfo completionMethod = GetMethodByName(completionMethodName);
if (completionMethod != null) {
return (actionName, controllerDescriptor) => new ReflectedAsyncActionDescriptor(entryMethod, completionMethod, actionName, controllerDescriptor);
}
else {
throw Error.AsyncActionMethodSelector_CouldNotFindMethod(completionMethodName, ControllerType);
}
}
// Fallback to synchronous method
return (actionName, controllerDescriptor) => new ReflectedActionDescriptor(entryMethod, actionName, controllerDescriptor);
}
private static string GetCanonicalMethodName(MethodInfo methodInfo) {
string methodName = methodInfo.Name;
return (IsAsyncSuffixedMethod(methodInfo))
? methodName.Substring(0, methodName.Length - "Async".Length)
: methodName;
}
internal List<MethodInfo> GetMatchingAliasedMethods(ControllerContext controllerContext, string actionName) {
// find all aliased methods which are opting in to this request
// to opt in, all attributes defined on the method must return true
var methods = from methodInfo in AliasedMethods
let attrs = ReflectedAttributeCache.GetActionNameSelectorAttributes(methodInfo)
where attrs.All(attr => attr.IsValidName(controllerContext, actionName, methodInfo))
select methodInfo;
return methods.ToList();
}
private static bool IsAsyncSuffixedMethod(MethodInfo methodInfo) {
return methodInfo.Name.EndsWith("Async", StringComparison.OrdinalIgnoreCase);
}
private static bool IsCompletedSuffixedMethod(MethodInfo methodInfo) {
return methodInfo.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase);
}
private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo) {
return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);
}
private MethodInfo GetMethodByName(string methodName) {
List<MethodInfo> methods = (from MethodInfo methodInfo in ControllerType.GetMember(methodName, MemberTypes.Method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase)
where IsValidActionMethod(methodInfo, false /* stripInfrastructureMethods */)
select methodInfo).ToList();
switch (methods.Count) {
case 0:
return null;
case 1:
return methods[0];
default:
throw CreateAmbiguousMethodMatchException(methods, methodName);
}
}
private static bool IsValidActionMethod(MethodInfo methodInfo) {
return IsValidActionMethod(methodInfo, true /* stripInfrastructureMethods */);
}
private static bool IsValidActionMethod(MethodInfo methodInfo, bool stripInfrastructureMethods) {
if (methodInfo.IsSpecialName) {
// not a normal method, e.g. a constructor or an event
return false;
}
if (methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(AsyncController))) {
// is a method on Object, ControllerBase, Controller, or AsyncController
return false;
};
if (stripInfrastructureMethods) {
if (IsCompletedSuffixedMethod(methodInfo)) {
// do not match FooCompleted() methods, as these are infrastructure methods
return false;
}
}
return true;
}
private void PopulateLookupTables() {
MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(GetCanonicalMethodName, StringComparer.OrdinalIgnoreCase);
}
private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos) {
// remove all methods which are opting out of this request
// to opt out, at least one attribute defined on the method must return false
List<MethodInfo> matchesWithSelectionAttributes = new List<MethodInfo>();
List<MethodInfo> matchesWithoutSelectionAttributes = new List<MethodInfo>();
foreach (MethodInfo methodInfo in methodInfos) {
ICollection<ActionMethodSelectorAttribute> attrs = ReflectedAttributeCache.GetActionMethodSelectorAttributes(methodInfo);
if (attrs.Count == 0) {
matchesWithoutSelectionAttributes.Add(methodInfo);
}
else if (attrs.All(attr => attr.IsValidForRequest(controllerContext, methodInfo))) {
matchesWithSelectionAttributes.Add(methodInfo);
}
}
// if a matching action method had a selection attribute, consider it more specific than a matching action method
// without a selection attribute
return (matchesWithSelectionAttributes.Count > 0) ? matchesWithSelectionAttributes : matchesWithoutSelectionAttributes;
}
}
}

View File

@@ -0,0 +1,270 @@
namespace System.Web.Mvc.Async {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
public class AsyncControllerActionInvoker : ControllerActionInvoker, IAsyncActionInvoker {
private static readonly object _invokeActionTag = new object();
private static readonly object _invokeActionMethodTag = new object();
private static readonly object _invokeActionMethodWithFiltersTag = new object();
public virtual IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state) {
if (controllerContext == null) {
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(actionName)) {
throw Error.ParameterCannotBeNullOrEmpty("actionName");
}
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
if (actionDescriptor != null) {
FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
Action continuation = null;
BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
try {
AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authContext.Result != null) {
// the auth filter signaled that we should let it short-circuit the request
continuation = () => InvokeActionResult(controllerContext, authContext.Result);
}
else {
if (controllerContext.Controller.ValidateRequest) {
ValidateRequest(controllerContext);
}
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
IAsyncResult asyncResult = BeginInvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters, asyncCallback, asyncState);
continuation = () => {
ActionExecutedContext postActionContext = EndInvokeActionMethodWithFilters(asyncResult);
InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
};
return asyncResult;
}
}
catch (ThreadAbortException) {
// This type of exception occurs as a result of Response.Redirect(), but we special-case so that
// the filters don't see this as an error.
throw;
}
catch (Exception ex) {
// something blew up, so execute the exception filters
ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
if (!exceptionContext.ExceptionHandled) {
throw;
}
continuation = () => InvokeActionResult(controllerContext, exceptionContext.Result);
}
return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback, asyncState);
};
EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
try {
continuation();
}
catch (ThreadAbortException) {
// This type of exception occurs as a result of Response.Redirect(), but we special-case so that
// the filters don't see this as an error.
throw;
}
catch (Exception ex) {
// something blew up, so execute the exception filters
ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
if (!exceptionContext.ExceptionHandled) {
throw;
}
InvokeActionResult(controllerContext, exceptionContext.Result);
}
return true;
};
return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
}
else {
// Notify the controller that no action was found.
return BeginInvokeAction_ActionNotFound(callback, state);
}
}
private static IAsyncResult BeginInvokeAction_ActionNotFound(AsyncCallback callback, object state) {
BeginInvokeDelegate beginDelegate = BeginInvokeAction_MakeSynchronousAsyncResult;
EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
return false;
};
return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
}
private static IAsyncResult BeginInvokeAction_MakeSynchronousAsyncResult(AsyncCallback callback, object state) {
SimpleAsyncResult asyncResult = new SimpleAsyncResult(state);
asyncResult.MarkCompleted(true /* completedSynchronously */, callback);
return asyncResult;
}
protected internal virtual IAsyncResult BeginInvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
AsyncActionDescriptor asyncActionDescriptor = actionDescriptor as AsyncActionDescriptor;
if (asyncActionDescriptor != null) {
return BeginInvokeAsynchronousActionMethod(controllerContext, asyncActionDescriptor, parameters, callback, state);
}
else {
return BeginInvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters, callback, state);
}
}
protected internal virtual IAsyncResult BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
Func<ActionExecutedContext> endContinuation = null;
BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
IAsyncResult innerAsyncResult = null;
Func<Func<ActionExecutedContext>> beginContinuation = () => {
innerAsyncResult = BeginInvokeActionMethod(controllerContext, actionDescriptor, parameters, asyncCallback, asyncState);
return () =>
new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {
Result = EndInvokeActionMethod(innerAsyncResult)
};
};
// need to reverse the filter list because the continuations are built up backward
Func<Func<ActionExecutedContext>> thunk = filters.Reverse().Aggregate(beginContinuation,
(next, filter) => () => InvokeActionMethodFilterAsynchronously(filter, preContext, next));
endContinuation = thunk();
if (innerAsyncResult != null) {
// we're just waiting for the inner result to complete
return innerAsyncResult;
}
else {
// something was short-circuited and the action was not called, so this was a synchronous operation
SimpleAsyncResult newAsyncResult = new SimpleAsyncResult(asyncState);
newAsyncResult.MarkCompleted(true /* completedSynchronously */, asyncCallback);
return newAsyncResult;
}
};
EndInvokeDelegate<ActionExecutedContext> endDelegate = delegate(IAsyncResult asyncResult) {
return endContinuation();
};
return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodWithFiltersTag);
}
private IAsyncResult BeginInvokeAsynchronousActionMethod(ControllerContext controllerContext, AsyncActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
return actionDescriptor.BeginExecute(controllerContext, parameters, asyncCallback, asyncState);
};
EndInvokeDelegate<ActionResult> endDelegate = delegate(IAsyncResult asyncResult) {
object returnValue = actionDescriptor.EndExecute(asyncResult);
ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
return result;
};
return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodTag);
}
private IAsyncResult BeginInvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
return AsyncResultWrapper.BeginSynchronous(callback, state,
() => InvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters),
_invokeActionMethodTag);
}
public virtual bool EndInvokeAction(IAsyncResult asyncResult) {
return AsyncResultWrapper.End<bool>(asyncResult, _invokeActionTag);
}
protected internal virtual ActionResult EndInvokeActionMethod(IAsyncResult asyncResult) {
return AsyncResultWrapper.End<ActionResult>(asyncResult, _invokeActionMethodTag);
}
protected internal virtual ActionExecutedContext EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) {
return AsyncResultWrapper.End<ActionExecutedContext>(asyncResult, _invokeActionMethodWithFiltersTag);
}
protected override ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext) {
Type controllerType = controllerContext.Controller.GetType();
ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(controllerType, () => new ReflectedAsyncControllerDescriptor(controllerType));
return controllerDescriptor;
}
internal static Func<ActionExecutedContext> InvokeActionMethodFilterAsynchronously(IActionFilter filter, ActionExecutingContext preContext, Func<Func<ActionExecutedContext>> nextInChain) {
filter.OnActionExecuting(preContext);
if (preContext.Result != null) {
ActionExecutedContext shortCircuitedPostContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */) {
Result = preContext.Result
};
return () => shortCircuitedPostContext;
}
// There is a nested try / catch block here that contains much the same logic as the outer block.
// Since an exception can occur on either side of the asynchronous invocation, we need guards on
// on both sides. In the code below, the second side is represented by the nested delegate. This
// is really just a parallel of the synchronous ControllerActionInvoker.InvokeActionMethodFilter()
// method.
try {
Func<ActionExecutedContext> continuation = nextInChain();
// add our own continuation, then return the new function
return () => {
ActionExecutedContext postContext;
bool wasError = true;
try {
postContext = continuation();
wasError = false;
}
catch (ThreadAbortException) {
// This type of exception occurs as a result of Response.Redirect(), but we special-case so that
// the filters don't see this as an error.
postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
filter.OnActionExecuted(postContext);
throw;
}
catch (Exception ex) {
postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
filter.OnActionExecuted(postContext);
if (!postContext.ExceptionHandled) {
throw;
}
}
if (!wasError) {
filter.OnActionExecuted(postContext);
}
return postContext;
};
}
catch (ThreadAbortException) {
// This type of exception occurs as a result of Response.Redirect(), but we special-case so that
// the filters don't see this as an error.
ActionExecutedContext postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
filter.OnActionExecuted(postContext);
throw;
}
catch (Exception ex) {
ActionExecutedContext postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
filter.OnActionExecuted(postContext);
if (postContext.ExceptionHandled) {
return () => postContext;
}
else {
throw;
}
}
}
private ActionResult InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
}
}
}

View File

@@ -0,0 +1,67 @@
namespace System.Web.Mvc.Async {
using System;
using System.Collections.Generic;
using System.Threading;
public class AsyncManager {
private readonly SynchronizationContext _syncContext;
// default timeout is 45 sec
// from: http://msdn.microsoft.com/en-us/library/system.web.ui.page.asynctimeout.aspx
private int _timeout = 45 * 1000;
public AsyncManager()
: this(null /* syncContext */) {
}
public AsyncManager(SynchronizationContext syncContext) {
_syncContext = syncContext ?? SynchronizationContextUtil.GetSynchronizationContext();
OutstandingOperations = new OperationCounter();
OutstandingOperations.Completed += delegate { Finish(); };
Parameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public OperationCounter OutstandingOperations {
get;
private set;
}
public IDictionary<string, object> Parameters {
get;
private set;
}
public event EventHandler Finished;
// the developer may call this function to signal that all operations are complete instead of
// waiting for the operation counter to reach zero
public virtual void Finish() {
EventHandler handler = Finished;
if (handler != null) {
handler(this, EventArgs.Empty);
}
}
// executes a callback in the current synchronization context, which gives access to HttpContext and related items
public virtual void Sync(Action action) {
_syncContext.Sync(action);
}
// measured in milliseconds, Timeout.Infinite means 'no timeout'
public int Timeout {
get {
return _timeout;
}
set {
if (value < -1) {
throw Error.AsyncCommon_InvalidTimeout("value");
}
_timeout = value;
}
}
}
}

View File

@@ -0,0 +1,256 @@
namespace System.Web.Mvc.Async {
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
// This class is used for the following pattern:
// public IAsyncResult BeginInner(..., callback, state);
// public TInnerResult EndInner(asyncResult);
// public IAsyncResult BeginOuter(..., callback, state);
// public TOuterResult EndOuter(asyncResult);
// That is, Begin/EndOuter() wrap Begin/EndInner(), potentially with pre- and post-processing.
internal static class AsyncResultWrapper {
// helper methods
private static Func<AsyncVoid> MakeVoidDelegate(Action action) {
return () =>
{
action();
return default(AsyncVoid);
};
}
private static EndInvokeDelegate<AsyncVoid> MakeVoidDelegate(EndInvokeDelegate endDelegate) {
return ar =>
{
endDelegate(ar);
return default(AsyncVoid);
};
}
// kicks off an asynchronous operation
public static IAsyncResult Begin<TResult>(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate) {
return Begin<TResult>(callback, state, beginDelegate, endDelegate, null /* tag */);
}
public static IAsyncResult Begin<TResult>(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate, object tag) {
return Begin<TResult>(callback, state, beginDelegate, endDelegate, tag, Timeout.Infinite);
}
public static IAsyncResult Begin<TResult>(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate, object tag, int timeout) {
WrappedAsyncResult<TResult> asyncResult = new WrappedAsyncResult<TResult>(beginDelegate, endDelegate, tag);
asyncResult.Begin(callback, state, timeout);
return asyncResult;
}
public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate) {
return Begin(callback, state, beginDelegate, endDelegate, null /* tag */);
}
public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, object tag) {
return Begin(callback, state, beginDelegate, endDelegate, tag, Timeout.Infinite);
}
public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, object tag, int timeout) {
return Begin<AsyncVoid>(callback, state, beginDelegate, MakeVoidDelegate(endDelegate), tag, timeout);
}
// wraps a synchronous operation in an asynchronous wrapper, but still completes synchronously
public static IAsyncResult BeginSynchronous<TResult>(AsyncCallback callback, object state, Func<TResult> func) {
return BeginSynchronous<TResult>(callback, state, func, null /* tag */);
}
public static IAsyncResult BeginSynchronous<TResult>(AsyncCallback callback, object state, Func<TResult> func, object tag) {
// Begin() doesn't perform any work on its own and returns immediately.
BeginInvokeDelegate beginDelegate = (asyncCallback, asyncState) =>
{
SimpleAsyncResult innerAsyncResult = new SimpleAsyncResult(asyncState);
innerAsyncResult.MarkCompleted(true /* completedSynchronously */, asyncCallback);
return innerAsyncResult;
};
// The End() method blocks.
EndInvokeDelegate<TResult> endDelegate = _ =>
{
return func();
};
WrappedAsyncResult<TResult> asyncResult = new WrappedAsyncResult<TResult>(beginDelegate, endDelegate, tag);
asyncResult.Begin(callback, state, Timeout.Infinite);
return asyncResult;
}
public static IAsyncResult BeginSynchronous(AsyncCallback callback, object state, Action action) {
return BeginSynchronous(callback, state, action, null /* tag */);
}
public static IAsyncResult BeginSynchronous(AsyncCallback callback, object state, Action action, object tag) {
return BeginSynchronous<AsyncVoid>(callback, state, MakeVoidDelegate(action), tag);
}
// completes an asynchronous operation
public static TResult End<TResult>(IAsyncResult asyncResult) {
return End<TResult>(asyncResult, null /* tag */);
}
public static TResult End<TResult>(IAsyncResult asyncResult, object tag) {
return WrappedAsyncResult<TResult>.Cast(asyncResult, tag).End();
}
public static void End(IAsyncResult asyncResult) {
End(asyncResult, null /* tag */);
}
public static void End(IAsyncResult asyncResult, object tag) {
End<AsyncVoid>(asyncResult, tag);
}
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "The Timer will be disposed of either when it fires or when the operation completes successfully.")]
private sealed class WrappedAsyncResult<TResult> : IAsyncResult {
private readonly BeginInvokeDelegate _beginDelegate;
private readonly object _beginDelegateLockObj = new object();
private readonly EndInvokeDelegate<TResult> _endDelegate;
private readonly SingleEntryGate _endExecutedGate = new SingleEntryGate(); // prevent End() from being called twice
private readonly SingleEntryGate _handleCallbackGate = new SingleEntryGate(); // prevent callback from being handled multiple times
private IAsyncResult _innerAsyncResult;
private AsyncCallback _originalCallback;
private readonly object _tag; // prevent an instance of this type from being passed to the wrong End() method
private volatile bool _timedOut;
private Timer _timer;
public WrappedAsyncResult(BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate, object tag) {
_beginDelegate = beginDelegate;
_endDelegate = endDelegate;
_tag = tag;
}
public object AsyncState {
get {
return _innerAsyncResult.AsyncState;
}
}
public WaitHandle AsyncWaitHandle {
get {
return _innerAsyncResult.AsyncWaitHandle;
}
}
public bool CompletedSynchronously {
get {
return _innerAsyncResult.CompletedSynchronously;
}
}
public bool IsCompleted {
get {
return _innerAsyncResult.IsCompleted;
}
}
// kicks off the process, instantiates a timer if requested
public void Begin(AsyncCallback callback, object state, int timeout) {
_originalCallback = callback;
bool completedSynchronously;
// Force the target Begin() operation to complete before the callback can continue,
// since the target operation might perform post-processing of the data.
lock (_beginDelegateLockObj) {
_innerAsyncResult = _beginDelegate(HandleAsynchronousCompletion, state);
completedSynchronously = _innerAsyncResult.CompletedSynchronously;
if (!completedSynchronously) {
if (timeout > Timeout.Infinite) {
CreateTimer(timeout);
}
}
}
if (completedSynchronously) {
if (callback != null) {
callback(this);
}
}
}
public static WrappedAsyncResult<TResult> Cast(IAsyncResult asyncResult, object tag) {
if (asyncResult == null) {
throw new ArgumentNullException("asyncResult");
}
WrappedAsyncResult<TResult> castResult = asyncResult as WrappedAsyncResult<TResult>;
if (castResult != null && Object.Equals(castResult._tag, tag)) {
return castResult;
}
else {
throw Error.AsyncCommon_InvalidAsyncResult("asyncResult");
}
}
private void CreateTimer(int timeout) {
// this method should be called within a lock(_beginDelegateLockObj)
_timer = new Timer(HandleTimeout, null, timeout, Timeout.Infinite /* disable periodic signaling */);
}
public TResult End() {
if (!_endExecutedGate.TryEnter()) {
throw Error.AsyncCommon_AsyncResultAlreadyConsumed();
}
if (_timedOut) {
throw new TimeoutException();
}
WaitForBeginToCompleteAndDestroyTimer();
return _endDelegate(_innerAsyncResult);
}
private void ExecuteAsynchronousCallback(bool timedOut) {
WaitForBeginToCompleteAndDestroyTimer();
if (_handleCallbackGate.TryEnter()) {
_timedOut = timedOut;
if (_originalCallback != null) {
_originalCallback(this);
}
}
}
private void HandleAsynchronousCompletion(IAsyncResult asyncResult) {
if (asyncResult.CompletedSynchronously) {
// If the operation completed synchronously, the WrappedAsyncResult.Begin() method will handle it.
return;
}
ExecuteAsynchronousCallback(false /* timedOut */);
}
private void HandleTimeout(object state) {
ExecuteAsynchronousCallback(true /* timedOut */);
}
private void WaitForBeginToCompleteAndDestroyTimer() {
lock (_beginDelegateLockObj) {
// Wait for the target Begin() method to complete, as it might be performing
// post-processing. This also forces a memory barrier, so _innerAsyncResult
// is guaranteed to be non-null at this point.
if (_timer != null) {
_timer.Dispose();
}
_timer = null;
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
namespace System.Web.Mvc.Async {
using System;
using System.Threading;
internal static class AsyncUtil {
public static void WaitForAsyncResultCompletion(IAsyncResult asyncResult, HttpApplication app) {
// based on HttpServerUtility.ExecuteInternal()
if (!asyncResult.IsCompleted) {
// suspend app lock while waiting, else might deadlock
bool needToRelock = false;
try {
// .NET 2.0+ will not allow a ThreadAbortException to be thrown while a
// thread is inside a finally block, so this pattern ensures that the
// value of 'needToRelock' is correct.
try { }
finally {
Monitor.Exit(app);
needToRelock = true;
}
WaitHandle waitHandle = asyncResult.AsyncWaitHandle;
if (waitHandle != null) {
waitHandle.WaitOne();
}
else {
while (!asyncResult.IsCompleted) {
Thread.Sleep(1);
}
}
}
finally {
if (needToRelock) {
Monitor.Enter(app);
}
}
}
}
public static AsyncCallback WrapCallbackForSynchronizedExecution(AsyncCallback callback, SynchronizationContext syncContext) {
if (callback == null || syncContext == null) {
return callback;
}
AsyncCallback newCallback = delegate(IAsyncResult asyncResult) {
if (asyncResult.CompletedSynchronously) {
callback(asyncResult);
}
else {
// Only take the application lock if this request completed asynchronously,
// else we might end up in a deadlock situation.
syncContext.Sync(() => callback(asyncResult));
}
};
return newCallback;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace System.Web.Mvc.Async {
using System;
// Dummy type used for passing something resembling 'void' to the async delegate functions
internal struct AsyncVoid {
}
}

View File

@@ -0,0 +1,5 @@
namespace System.Web.Mvc.Async {
using System;
internal delegate IAsyncResult BeginInvokeDelegate(AsyncCallback callback, object state);
}

View File

@@ -0,0 +1,5 @@
namespace System.Web.Mvc.Async {
using System;
internal delegate void EndInvokeDelegate(IAsyncResult asyncResult);
}

View File

@@ -0,0 +1,5 @@
namespace System.Web.Mvc.Async {
using System;
internal delegate TResult EndInvokeDelegate<TResult>(IAsyncResult asyncResult);
}

View File

@@ -0,0 +1,8 @@
namespace System.Web.Mvc.Async {
using System;
public interface IAsyncActionInvoker : IActionInvoker {
IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state);
bool EndInvokeAction(IAsyncResult asyncResult);
}
}

View File

@@ -0,0 +1,8 @@
namespace System.Web.Mvc.Async {
using System.Web.Routing;
public interface IAsyncController : IController {
IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
void EndExecute(IAsyncResult asyncResult);
}
}

View File

@@ -0,0 +1,10 @@
namespace System.Web.Mvc.Async {
public interface IAsyncManagerContainer {
AsyncManager AsyncManager {
get;
}
}
}

View File

@@ -0,0 +1,50 @@
namespace System.Web.Mvc.Async {
using System;
using System.Threading;
public sealed class OperationCounter {
private int _count;
public int Count {
get {
return Thread.VolatileRead(ref _count);
}
}
public event EventHandler Completed;
private int AddAndExecuteCallbackIfCompleted(int value) {
int newCount = Interlocked.Add(ref _count, value);
if (newCount == 0) {
OnCompleted();
}
return newCount;
}
public int Decrement() {
return AddAndExecuteCallbackIfCompleted(-1);
}
public int Decrement(int value) {
return AddAndExecuteCallbackIfCompleted(-value);
}
public int Increment() {
return AddAndExecuteCallbackIfCompleted(1);
}
public int Increment(int value) {
return AddAndExecuteCallbackIfCompleted(value);
}
private void OnCompleted() {
EventHandler handler = Completed;
if (handler != null) {
handler(this, EventArgs.Empty);
}
}
}
}

View File

@@ -0,0 +1,185 @@
namespace System.Web.Mvc.Async {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
public class ReflectedAsyncActionDescriptor : AsyncActionDescriptor {
private readonly object _executeTag = new object();
private readonly string _actionName;
private readonly ControllerDescriptor _controllerDescriptor;
private ParameterDescriptor[] _parametersCache;
private readonly Lazy<string> _uniqueId;
public ReflectedAsyncActionDescriptor(MethodInfo asyncMethodInfo, MethodInfo completedMethodInfo, string actionName, ControllerDescriptor controllerDescriptor)
: this(asyncMethodInfo, completedMethodInfo, actionName, controllerDescriptor, true /* validateMethods */) {
}
internal ReflectedAsyncActionDescriptor(MethodInfo asyncMethodInfo, MethodInfo completedMethodInfo, string actionName, ControllerDescriptor controllerDescriptor, bool validateMethods) {
if (asyncMethodInfo == null) {
throw new ArgumentNullException("asyncMethodInfo");
}
if (completedMethodInfo == null) {
throw new ArgumentNullException("completedMethodInfo");
}
if (String.IsNullOrEmpty(actionName)) {
throw Error.ParameterCannotBeNullOrEmpty("actionName");
}
if (controllerDescriptor == null) {
throw new ArgumentNullException("controllerDescriptor");
}
if (validateMethods) {
string asyncFailedMessage = VerifyActionMethodIsCallable(asyncMethodInfo);
if (asyncFailedMessage != null) {
throw new ArgumentException(asyncFailedMessage, "asyncMethodInfo");
}
string completedFailedMessage = VerifyActionMethodIsCallable(completedMethodInfo);
if (completedFailedMessage != null) {
throw new ArgumentException(completedFailedMessage, "completedMethodInfo");
}
}
AsyncMethodInfo = asyncMethodInfo;
CompletedMethodInfo = completedMethodInfo;
_actionName = actionName;
_controllerDescriptor = controllerDescriptor;
_uniqueId = new Lazy<string>(CreateUniqueId);
}
public override string ActionName {
get {
return _actionName;
}
}
public MethodInfo AsyncMethodInfo {
get;
private set;
}
public MethodInfo CompletedMethodInfo {
get;
private set;
}
public override ControllerDescriptor ControllerDescriptor {
get {
return _controllerDescriptor;
}
}
public override string UniqueId {
get {
return _uniqueId.Value;
}
}
public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
if (controllerContext == null) {
throw new ArgumentNullException("controllerContext");
}
if (parameters == null) {
throw new ArgumentNullException("parameters");
}
AsyncManager asyncManager = GetAsyncManager(controllerContext.Controller);
BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
// call the XxxAsync() method
ParameterInfo[] parameterInfos = AsyncMethodInfo.GetParameters();
var rawParameterValues = from parameterInfo in parameterInfos
select ExtractParameterFromDictionary(parameterInfo, parameters, AsyncMethodInfo);
object[] parametersArray = rawParameterValues.ToArray();
TriggerListener listener = new TriggerListener();
SimpleAsyncResult asyncResult = new SimpleAsyncResult(asyncState);
// hook the Finished event to notify us upon completion
Trigger finishTrigger = listener.CreateTrigger();
asyncManager.Finished += delegate { finishTrigger.Fire(); };
asyncManager.OutstandingOperations.Increment();
// to simplify the logic, force the rest of the pipeline to execute in an asynchronous callback
listener.SetContinuation(() => ThreadPool.QueueUserWorkItem(_ => asyncResult.MarkCompleted(false /* completedSynchronously */, asyncCallback)));
// the inner operation might complete synchronously, so all setup work has to be done before this point
ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(AsyncMethodInfo);
dispatcher.Execute(controllerContext.Controller, parametersArray); // ignore return value from this method
// now that the XxxAsync() method has completed, kick off any pending operations
asyncManager.OutstandingOperations.Decrement();
listener.Activate();
return asyncResult;
};
EndInvokeDelegate<object> endDelegate = delegate(IAsyncResult asyncResult) {
// call the XxxCompleted() method
ParameterInfo[] completionParametersInfos = CompletedMethodInfo.GetParameters();
var rawCompletionParameterValues = from parameterInfo in completionParametersInfos
select ExtractParameterOrDefaultFromDictionary(parameterInfo, asyncManager.Parameters);
object[] completionParametersArray = rawCompletionParameterValues.ToArray();
ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(CompletedMethodInfo);
object actionReturnValue = dispatcher.Execute(controllerContext.Controller, completionParametersArray);
return actionReturnValue;
};
return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _executeTag, asyncManager.Timeout);
}
private string CreateUniqueId() {
return base.UniqueId + DescriptorUtil.CreateUniqueId(AsyncMethodInfo, CompletedMethodInfo);
}
public override object EndExecute(IAsyncResult asyncResult) {
return AsyncResultWrapper.End<object>(asyncResult, _executeTag);
}
public override object[] GetCustomAttributes(bool inherit) {
return AsyncMethodInfo.GetCustomAttributes(inherit);
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
return AsyncMethodInfo.GetCustomAttributes(attributeType, inherit);
}
internal override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache) {
if (useCache && GetType() == typeof(ReflectedAsyncActionDescriptor)) {
// Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
return ReflectedAttributeCache.GetMethodFilterAttributes(AsyncMethodInfo);
}
return base.GetFilterAttributes(useCache);
}
public override ParameterDescriptor[] GetParameters() {
ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
// need to clone array so that user modifications aren't accidentally stored
return (ParameterDescriptor[])parameters.Clone();
}
public override ICollection<ActionSelector> GetSelectors() {
// By default, we only look at filters on the XxxAsync() method.
ActionMethodSelectorAttribute[] attrs = (ActionMethodSelectorAttribute[])AsyncMethodInfo.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true /* inherit */);
ActionSelector[] selectors = Array.ConvertAll(attrs, attr => (ActionSelector)(controllerContext => attr.IsValidForRequest(controllerContext, AsyncMethodInfo)));
return selectors;
}
public override bool IsDefined(Type attributeType, bool inherit) {
return AsyncMethodInfo.IsDefined(attributeType, inherit);
}
private ParameterDescriptor[] LazilyFetchParametersCollection() {
return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
ref _parametersCache /* cacheLocation */,
AsyncMethodInfo.GetParameters /* initializer */,
parameterInfo => new ReflectedParameterDescriptor(parameterInfo, this) /* converter */);
}
}
}

View File

@@ -0,0 +1,69 @@
namespace System.Web.Mvc.Async {
using System;
using System.Collections.Generic;
public class ReflectedAsyncControllerDescriptor : ControllerDescriptor {
private static readonly ActionDescriptor[] _emptyCanonicalActions = new ActionDescriptor[0];
private readonly Type _controllerType;
private readonly AsyncActionMethodSelector _selector;
public ReflectedAsyncControllerDescriptor(Type controllerType) {
if (controllerType == null) {
throw new ArgumentNullException("controllerType");
}
_controllerType = controllerType;
_selector = new AsyncActionMethodSelector(_controllerType);
}
public sealed override Type ControllerType {
get {
return _controllerType;
}
}
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) {
if (controllerContext == null) {
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(actionName)) {
throw Error.ParameterCannotBeNullOrEmpty("actionName");
}
ActionDescriptorCreator creator = _selector.FindAction(controllerContext, actionName);
if (creator == null) {
return null;
}
return creator(actionName, this);
}
public override ActionDescriptor[] GetCanonicalActions() {
// everything is looked up dymanically, so there are no 'canonical' actions
return _emptyCanonicalActions;
}
public override object[] GetCustomAttributes(bool inherit) {
return ControllerType.GetCustomAttributes(inherit);
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
return ControllerType.GetCustomAttributes(attributeType, inherit);
}
internal override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache) {
if (useCache && GetType() == typeof(ReflectedAsyncControllerDescriptor)) {
// Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
return ReflectedAttributeCache.GetTypeFilterAttributes(ControllerType);
}
return base.GetFilterAttributes(useCache);
}
public override bool IsDefined(Type attributeType, bool inherit) {
return ControllerType.IsDefined(attributeType, inherit);
}
}
}

View File

@@ -0,0 +1,55 @@
namespace System.Web.Mvc.Async {
using System;
using System.Threading;
internal sealed class SimpleAsyncResult : IAsyncResult {
private readonly object _asyncState;
private bool _completedSynchronously;
private volatile bool _isCompleted;
public SimpleAsyncResult(object asyncState) {
_asyncState = asyncState;
}
public object AsyncState {
get {
return _asyncState;
}
}
// ASP.NET IAsyncResult objects should never expose a WaitHandle due to potential deadlocking
public WaitHandle AsyncWaitHandle {
get {
return null;
}
}
public bool CompletedSynchronously {
get {
return _completedSynchronously;
}
}
public bool IsCompleted {
get {
return _isCompleted;
}
}
// Proper order of execution:
// 1. Set the CompletedSynchronously property to the correct value
// 2. Set the IsCompleted flag
// 3. Execute the callback
// 4. Signal the WaitHandle (which we don't have)
public void MarkCompleted(bool completedSynchronously, AsyncCallback callback) {
_completedSynchronously = completedSynchronously;
_isCompleted = true;
if (callback != null) {
callback(this);
}
}
}
}

View File

@@ -0,0 +1,20 @@
namespace System.Web.Mvc.Async {
using System;
using System.Threading;
// used to synchronize access to a single-use consumable resource
internal sealed class SingleEntryGate {
private const int NOT_ENTERED = 0;
private const int ENTERED = 1;
private int _status;
// returns true if this is the first call to TryEnter(), false otherwise
public bool TryEnter() {
int oldStatus = Interlocked.Exchange(ref _status, ENTERED);
return (oldStatus == NOT_ENTERED);
}
}
}

View File

@@ -0,0 +1,47 @@
namespace System.Web.Mvc.Async {
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
internal static class SynchronizationContextUtil {
public static SynchronizationContext GetSynchronizationContext() {
// In a runtime environment, SynchronizationContext.Current will be set to an instance
// of AspNetSynchronizationContext. In a unit test environment, the Current property
// won't be set and we have to create one on the fly.
return SynchronizationContext.Current ?? new SynchronizationContext();
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is swallowed and immediately re-thrown")]
public static T Sync<T>(this SynchronizationContext syncContext, Func<T> func) {
T theValue = default(T);
Exception thrownException = null;
syncContext.Send(o =>
{
try {
theValue = func();
}
catch (Exception ex) {
// by default, the AspNetSynchronizationContext type will swallow thrown exceptions,
// so we need to save and propagate them
thrownException = ex;
}
}, null);
if (thrownException != null) {
throw Error.SynchronizationContextUtil_ExceptionThrown(thrownException);
}
return theValue;
}
public static void Sync(this SynchronizationContext syncContext, Action action) {
Sync<AsyncVoid>(syncContext, () =>
{
action();
return default(AsyncVoid);
});
}
}
}

Some files were not shown because too many files have changed in this diff Show More