在f#中,如何判断一个对象是否为Async,如何将其转换为Async?

时间:2022-05-02 13:27:49

I'm currently trying to create an IHttpActionInvoker for use with ASP.NET Web API that will allow a result to be an Async<'T>. At the moment, I'm ignoring conversion of IHttpActionResults and only care about HttpResponseMessage and a value of type 'T. I currently have the following implementation:

我目前正在尝试创建一个用于ASP的IHttpActionInvoker。允许结果为异步<'T>的NET Web API。目前,我忽略了IHttpActionResults的转换,只关心HttpResponseMessage和类型为t的值。

type AsyncApiActionInvoker() =
    inherit Controllers.ApiControllerActionInvoker()

    override x.InvokeActionAsync(actionContext, cancellationToken) =
        if actionContext = null then
            raise <| ArgumentNullException("actionContext")

        let actionDescriptor = actionContext.ActionDescriptor
        Contract.Assert(actionDescriptor <> null)

        if actionDescriptor.ReturnType = typeof<Async<HttpResponseMessage>> then

            let controllerContext = actionContext.ControllerContext
            Contract.Assert(controllerContext <> null)

            let task = async {
                let! asyncResult = Async.AwaitTask <| actionDescriptor.ExecuteAsync(controllerContext, actionContext.ActionArguments, cancellationToken)
                // For now, throw if the result is an IHttpActionResult.
                if typeof<IHttpActionResult>.IsAssignableFrom(actionDescriptor.ReturnType) then
                    raise <| InvalidOperationException("IHttpResult is not supported when returning an Async")
                let! result = asyncResult :?> Async<HttpResponseMessage>
                return actionDescriptor.ResultConverter.Convert(controllerContext, result) }

            Async.StartAsTask(task, cancellationToken = cancellationToken)

        else base.InvokeActionAsync(actionContext, cancellationToken)

This works for Async<HttpResponseMessage> only. If I try to cast to Async<_>, I get an exception stating that I can't cast to Async<obj>. I also cannot correctly detect whether or not the actionDescriptor.ReturnType is an Async<_>. This does not surprise me, but I'm not sure how to get around the problem.

这只适用于异步 。如果我尝试强制转换为Async<_>,我就会得到一个异常,说明我不能转换为Async 。我也不能正确地检测actionDescriptor是否为actionDescriptor。ReturnType是一个异步< _ >。这并不让我惊讶,但我不知道如何解决这个问题。

2 个解决方案

#1


2  

as an option (browser-compiled code, may contain errors)

作为选项(浏览器编译的代码可能包含错误)

let (|Async|_|) (ty: Type) =
    if ty.IsGenericType && ty.GetGenericTypeDefinition() = typedefof<Async<_>> then
        Some (ty.GetGenericArguments().[0])
    else 
        None

type AsyncApiActionInvoker() =
    inherit Controllers.ApiControllerActionInvoker()

    static let AsTaskMethod = typeof<AsyncApiActionInvoker>.GetMethod("AsTask")

    static member AsTask<'T> (actionContext: Controllers.HttpActionContext, cancellationToken: CancellationToken) =
        let action = async {
            let task = 
                actionContext.ActionDescriptor.ExecuteAsync(
                    actionContext.ControllerContext, 
                    actionContext.ActionArguments, 
                    cancellationToken
                )
            let! result = Async.AwaitTask task
            let! asyncResult = result :?> Async<'T>
            return actionContext.ActionDescriptor.ResultConverter.Convert(actionContext.ControllerContext, box asyncResult)
        }

        Async.StartAsTask(action, cancellationToken = cancellationToken)

    override x.InvokeActionAsync(actionContext, cancellationToken) =
        if actionContext = null then
            raise <| ArgumentNullException("actionContext")

        match actionContext.ActionDescriptor.ReturnType with
        | Async resultType ->
            let specialized = AsTaskMethod.MakeGenericMethod(resultType)
            downcast specialized.Invoke(null, [|actionContext, cancellationToken|])
        | _ -> base.InvokeActionAsync(actionContext, cancellationToken)

#2


0  

After several helpful tips from outside *, I came up with the following solution that appears to work. I'm not thrilled with it, but it does the job. I'd appreciate any tips or pointers:

在*外提供了一些有用的提示之后,我找到了以下似乎有效的解决方案。我对它不感兴趣,但它能胜任这份工作。如有任何建议或建议,我将不胜感激。

type AsyncApiActionInvoker() =
    inherit Controllers.ApiControllerActionInvoker()

    static member internal GetResultConverter(instanceType: Type, actionDescriptor: HttpActionDescriptor) : IActionResultConverter =
        if instanceType <> null && instanceType.IsGenericParameter then
            raise <| InvalidOperationException()

        if instanceType = null || typeof<HttpResponseMessage>.IsAssignableFrom instanceType then
            actionDescriptor.ResultConverter
        else
            let valueConverterType = typedefof<ValueResultConverter<_>>.MakeGenericType instanceType
            let newInstanceExpression = Expression.New valueConverterType
            let ctor = Expression.Lambda<Func<IActionResultConverter>>(newInstanceExpression).Compile()
            ctor.Invoke()

    static member internal StartAsTask<'T>(task, resultConverter: IActionResultConverter, controllerContext, cancellationToken) =
        let computation = async {
            let! comp = Async.AwaitTask task
            let! (value: 'T) = unbox comp
            return resultConverter.Convert(controllerContext, value) }
        Async.StartAsTask(computation, cancellationToken = cancellationToken)

    override this.InvokeActionAsync(actionContext, cancellationToken) =
        if actionContext = null then
            raise <| ArgumentNullException("actionContext")

        let actionDescriptor = actionContext.ActionDescriptor
        Contract.Assert(actionDescriptor <> null)

        let returnType = actionDescriptor.ReturnType
        // For now, throw if the result is an IHttpActionResult.
        if typeof<IHttpActionResult>.IsAssignableFrom(returnType) then
            raise <| InvalidOperationException("IHttpResult is not supported when returning an Async")

        if returnType.IsGenericType && returnType.GetGenericTypeDefinition() = typedefof<Async<_>> then
            let controllerContext = actionContext.ControllerContext
            Contract.Assert(controllerContext <> null)

            let computation = actionDescriptor.ExecuteAsync(controllerContext, actionContext.ActionArguments, cancellationToken)
            let innerReturnType = returnType.GetGenericArguments().[0]
            let converter = AsyncApiActionInvoker.GetResultConverter(innerReturnType, actionDescriptor)
            this.GetType()
                .GetMethod("StartAsTask", BindingFlags.NonPublic ||| BindingFlags.Static)
                .MakeGenericMethod(innerReturnType)
                .Invoke(null, [| computation; converter; controllerContext; cancellationToken |])
                |> unbox

        else base.InvokeActionAsync(actionContext, cancellationToken)

I hope this helps someone else!

我希望这能帮助别人!

#1


2  

as an option (browser-compiled code, may contain errors)

作为选项(浏览器编译的代码可能包含错误)

let (|Async|_|) (ty: Type) =
    if ty.IsGenericType && ty.GetGenericTypeDefinition() = typedefof<Async<_>> then
        Some (ty.GetGenericArguments().[0])
    else 
        None

type AsyncApiActionInvoker() =
    inherit Controllers.ApiControllerActionInvoker()

    static let AsTaskMethod = typeof<AsyncApiActionInvoker>.GetMethod("AsTask")

    static member AsTask<'T> (actionContext: Controllers.HttpActionContext, cancellationToken: CancellationToken) =
        let action = async {
            let task = 
                actionContext.ActionDescriptor.ExecuteAsync(
                    actionContext.ControllerContext, 
                    actionContext.ActionArguments, 
                    cancellationToken
                )
            let! result = Async.AwaitTask task
            let! asyncResult = result :?> Async<'T>
            return actionContext.ActionDescriptor.ResultConverter.Convert(actionContext.ControllerContext, box asyncResult)
        }

        Async.StartAsTask(action, cancellationToken = cancellationToken)

    override x.InvokeActionAsync(actionContext, cancellationToken) =
        if actionContext = null then
            raise <| ArgumentNullException("actionContext")

        match actionContext.ActionDescriptor.ReturnType with
        | Async resultType ->
            let specialized = AsTaskMethod.MakeGenericMethod(resultType)
            downcast specialized.Invoke(null, [|actionContext, cancellationToken|])
        | _ -> base.InvokeActionAsync(actionContext, cancellationToken)

#2


0  

After several helpful tips from outside *, I came up with the following solution that appears to work. I'm not thrilled with it, but it does the job. I'd appreciate any tips or pointers:

在*外提供了一些有用的提示之后,我找到了以下似乎有效的解决方案。我对它不感兴趣,但它能胜任这份工作。如有任何建议或建议,我将不胜感激。

type AsyncApiActionInvoker() =
    inherit Controllers.ApiControllerActionInvoker()

    static member internal GetResultConverter(instanceType: Type, actionDescriptor: HttpActionDescriptor) : IActionResultConverter =
        if instanceType <> null && instanceType.IsGenericParameter then
            raise <| InvalidOperationException()

        if instanceType = null || typeof<HttpResponseMessage>.IsAssignableFrom instanceType then
            actionDescriptor.ResultConverter
        else
            let valueConverterType = typedefof<ValueResultConverter<_>>.MakeGenericType instanceType
            let newInstanceExpression = Expression.New valueConverterType
            let ctor = Expression.Lambda<Func<IActionResultConverter>>(newInstanceExpression).Compile()
            ctor.Invoke()

    static member internal StartAsTask<'T>(task, resultConverter: IActionResultConverter, controllerContext, cancellationToken) =
        let computation = async {
            let! comp = Async.AwaitTask task
            let! (value: 'T) = unbox comp
            return resultConverter.Convert(controllerContext, value) }
        Async.StartAsTask(computation, cancellationToken = cancellationToken)

    override this.InvokeActionAsync(actionContext, cancellationToken) =
        if actionContext = null then
            raise <| ArgumentNullException("actionContext")

        let actionDescriptor = actionContext.ActionDescriptor
        Contract.Assert(actionDescriptor <> null)

        let returnType = actionDescriptor.ReturnType
        // For now, throw if the result is an IHttpActionResult.
        if typeof<IHttpActionResult>.IsAssignableFrom(returnType) then
            raise <| InvalidOperationException("IHttpResult is not supported when returning an Async")

        if returnType.IsGenericType && returnType.GetGenericTypeDefinition() = typedefof<Async<_>> then
            let controllerContext = actionContext.ControllerContext
            Contract.Assert(controllerContext <> null)

            let computation = actionDescriptor.ExecuteAsync(controllerContext, actionContext.ActionArguments, cancellationToken)
            let innerReturnType = returnType.GetGenericArguments().[0]
            let converter = AsyncApiActionInvoker.GetResultConverter(innerReturnType, actionDescriptor)
            this.GetType()
                .GetMethod("StartAsTask", BindingFlags.NonPublic ||| BindingFlags.Static)
                .MakeGenericMethod(innerReturnType)
                .Invoke(null, [| computation; converter; controllerContext; cancellationToken |])
                |> unbox

        else base.InvokeActionAsync(actionContext, cancellationToken)

I hope this helps someone else!

我希望这能帮助别人!